mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-25 10:46:15 +03:00
- Merged custom-search branch. New search plugins management system
This commit is contained in:
parent
b7fd7d9837
commit
ff4ab915a2
28 changed files with 1427 additions and 934 deletions
|
@ -5,6 +5,7 @@
|
||||||
- FEATURE: Bittorrent FAST extension support
|
- FEATURE: Bittorrent FAST extension support
|
||||||
- FEATURE: Added RSS support
|
- FEATURE: Added RSS support
|
||||||
- FEATURE: Support files prioritizing in a torrent
|
- FEATURE: Support files prioritizing in a torrent
|
||||||
|
- FEATURE: Brand new search engine plugins system
|
||||||
- FEATURE: Finished torrents are now moved to another tab for seeding
|
- FEATURE: Finished torrents are now moved to another tab for seeding
|
||||||
- FEATURE: Display more infos about the torrent in its properties
|
- FEATURE: Display more infos about the torrent in its properties
|
||||||
- FEATURE: Allow the user to edit torrents' trackers
|
- FEATURE: Allow the user to edit torrents' trackers
|
||||||
|
|
2
TODO
2
TODO
|
@ -38,7 +38,6 @@
|
||||||
- Allow to limit the number of downloading torrents simultaneously (other are paused until a download finishes)
|
- Allow to limit the number of downloading torrents simultaneously (other are paused until a download finishes)
|
||||||
- Add "Mark all as read" feature for RSS
|
- Add "Mark all as read" feature for RSS
|
||||||
- Allow to customize lists refreshing interval (in options)
|
- Allow to customize lists refreshing interval (in options)
|
||||||
- Use search engines as plugins (split them, load them dynamically) to allow the user to add some
|
|
||||||
|
|
||||||
// in v1.0.0 (partial) - WIP
|
// in v1.0.0 (partial) - WIP
|
||||||
- Check storage st creation + hasher in torrent creation
|
- Check storage st creation + hasher in torrent creation
|
||||||
|
@ -80,6 +79,7 @@ beta5->beta6 changelog:
|
||||||
- FEATURE: A lot of code optimization (CPU & memory usage)
|
- FEATURE: A lot of code optimization (CPU & memory usage)
|
||||||
- FEATURE: Added support for .ico format (useful for RSS favicons)
|
- FEATURE: Added support for .ico format (useful for RSS favicons)
|
||||||
- FEATURE: Replaced Meganova search engine by TorrentReactor
|
- FEATURE: Replaced Meganova search engine by TorrentReactor
|
||||||
|
- FEATURE: Brand new search engine plugins system
|
||||||
- I18N: Updated Greek, Dutch and Romanian translation
|
- I18N: Updated Greek, Dutch and Romanian translation
|
||||||
- I18N: Removed no longer maintained Traditional chinese translation
|
- I18N: Removed no longer maintained Traditional chinese translation
|
||||||
- BUGFIX: Made torrent deletion from hard-drive safer
|
- BUGFIX: Made torrent deletion from hard-drive safer
|
||||||
|
|
117
src/engineSelect.ui
Normal file
117
src/engineSelect.ui
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<ui version="4.0" >
|
||||||
|
<class>engineSelect</class>
|
||||||
|
<widget class="QDialog" name="engineSelect" >
|
||||||
|
<property name="geometry" >
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>527</width>
|
||||||
|
<height>254</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle" >
|
||||||
|
<string>Search plugins</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" >
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lbl_engines" >
|
||||||
|
<property name="font" >
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
<underline>true</underline>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text" >
|
||||||
|
<string>Installed search engines:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="pluginsTree" >
|
||||||
|
<property name="contextMenuPolicy" >
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode" >
|
||||||
|
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable" >
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text" >
|
||||||
|
<string>Name</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text" >
|
||||||
|
<string>Url</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text" >
|
||||||
|
<string>Enabled</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="getNewEngine_lbl" >
|
||||||
|
<property name="font" >
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text" >
|
||||||
|
<string>You can get new search engine plugins here: <a href="http:plugins.qbittorrent.org">http://plugins.qbittorrent.org</a></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" >
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="installButton" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Install a new one</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="updateButton" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Check for updates</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="closeButton" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
<action name="actionEnable" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Enable</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionDisable" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Disable</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionUninstall" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Uninstall</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
443
src/engineSelectDlg.cpp
Normal file
443
src/engineSelectDlg.cpp
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
/*
|
||||||
|
* 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 "engineSelectDlg.h"
|
||||||
|
#include "downloadThread.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#ifdef HAVE_MAGICK
|
||||||
|
#include <Magick++.h>
|
||||||
|
using namespace Magick;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ENGINE_NAME 0
|
||||||
|
#define ENGINE_URL 1
|
||||||
|
#define ENGINE_STATE 2
|
||||||
|
|
||||||
|
engineSelectDlg::engineSelectDlg(QWidget *parent) : QDialog(parent) {
|
||||||
|
setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
pluginsTree->header()->resizeSection(0, 170);
|
||||||
|
pluginsTree->header()->resizeSection(1, 220);
|
||||||
|
actionEnable->setIcon(QIcon(QString::fromUtf8(":/Icons/button_ok.png")));
|
||||||
|
actionDisable->setIcon(QIcon(QString::fromUtf8(":/Icons/button_cancel.png")));
|
||||||
|
actionUninstall->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/remove.png")));
|
||||||
|
connect(actionEnable, SIGNAL(triggered()), this, SLOT(enableSelection()));
|
||||||
|
connect(actionDisable, SIGNAL(triggered()), this, SLOT(disableSelection()));
|
||||||
|
connect(pluginsTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayContextMenu(const QPoint&)));
|
||||||
|
downloader = new downloadThread(this);
|
||||||
|
connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processDownloadedFile(QString, QString)));
|
||||||
|
connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
|
||||||
|
loadSettings();
|
||||||
|
loadSupportedSearchEngines();
|
||||||
|
connect(pluginsTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(toggleEngineState(QTreeWidgetItem*, int)));
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
engineSelectDlg::~engineSelectDlg() {
|
||||||
|
qDebug("Destroying engineSelectDlg");
|
||||||
|
saveSettings();
|
||||||
|
emit enginesChanged();
|
||||||
|
qDebug("Before deleting downloader");
|
||||||
|
delete downloader;
|
||||||
|
qDebug("Engine plugins dialog destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::loadSettings() {
|
||||||
|
QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent"));
|
||||||
|
known_engines = settings.value(QString::fromUtf8("SearchEngines/knownEngines"), QStringList()).toStringList();
|
||||||
|
known_enginesEnabled = settings.value(QString::fromUtf8("SearchEngines/knownEnginesEnabled"), QList<QVariant>()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::saveSettings() {
|
||||||
|
QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent"));
|
||||||
|
settings.setValue(QString::fromUtf8("SearchEngines/knownEngines"), installed_engines);
|
||||||
|
settings.setValue(QString::fromUtf8("SearchEngines/knownEnginesEnabled"), enginesEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::on_updateButton_clicked() {
|
||||||
|
// Download version file from primary server
|
||||||
|
downloader->downloadUrl("http://www.dchris.eu/search_engine/versions.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::toggleEngineState(QTreeWidgetItem *item, int) {
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
bool new_val = !enginesEnabled.at(index).toBool();
|
||||||
|
enginesEnabled.replace(index, QVariant(new_val));
|
||||||
|
QString enabledTxt;
|
||||||
|
if(new_val){
|
||||||
|
enabledTxt = tr("True");
|
||||||
|
setRowColor(index, "green");
|
||||||
|
}else{
|
||||||
|
enabledTxt = tr("False");
|
||||||
|
setRowColor(index, "red");
|
||||||
|
}
|
||||||
|
item->setText(ENGINE_STATE, enabledTxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::displayContextMenu(const QPoint& pos) {
|
||||||
|
QMenu myContextMenu(this);
|
||||||
|
QModelIndex index;
|
||||||
|
// Enable/disable pause/start action given the DL state
|
||||||
|
QList<QTreeWidgetItem *> items = pluginsTree->selectedItems();
|
||||||
|
bool has_enable = false, has_disable = false;
|
||||||
|
QTreeWidgetItem *item;
|
||||||
|
foreach(item, items) {
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
if(enginesEnabled.at(index).toBool() and !has_disable) {
|
||||||
|
myContextMenu.addAction(actionDisable);
|
||||||
|
has_disable = true;
|
||||||
|
}
|
||||||
|
if(!enginesEnabled.at(index).toBool() and !has_enable) {
|
||||||
|
myContextMenu.addAction(actionEnable);
|
||||||
|
has_enable = true;
|
||||||
|
}
|
||||||
|
if(has_enable && has_disable) break;
|
||||||
|
}
|
||||||
|
myContextMenu.addSeparator();
|
||||||
|
myContextMenu.addAction(actionUninstall);
|
||||||
|
myContextMenu.exec(mapToGlobal(pos)+QPoint(12, 58));
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::on_closeButton_clicked() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::on_actionUninstall_triggered() {
|
||||||
|
QList<QTreeWidgetItem *> items = pluginsTree->selectedItems();
|
||||||
|
QTreeWidgetItem *item;
|
||||||
|
bool change = false;
|
||||||
|
bool error = false;
|
||||||
|
foreach(item, items) {
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
QString name = installed_engines.at(index);
|
||||||
|
if(QFile::exists(":/search_engine/engines/"+name+".py")) {
|
||||||
|
error = true;
|
||||||
|
// Disable it instead
|
||||||
|
enginesEnabled.replace(index, QVariant(false));
|
||||||
|
item->setText(ENGINE_STATE, tr("False"));
|
||||||
|
setRowColor(index, "red");
|
||||||
|
continue;
|
||||||
|
}else {
|
||||||
|
// Proceed with uninstall
|
||||||
|
// remove it from hard drive
|
||||||
|
QFile::remove(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+name+".py");
|
||||||
|
if(QFile::exists(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+name+".png")) {
|
||||||
|
QFile::remove(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+name+".png");
|
||||||
|
}
|
||||||
|
// Remove it from lists
|
||||||
|
installed_engines.removeAt(index);
|
||||||
|
enginesEnabled.removeAt(index);
|
||||||
|
pluginsTree->takeTopLevelItem(index);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(change)
|
||||||
|
saveSettings();
|
||||||
|
if(error)
|
||||||
|
QMessageBox::warning(0, tr("Uninstall warning"), tr("Some plugins could not be uninstalled because they are included in qBittorrent.\n Only the ones you added yourself can be uninstalled.\nHowever, those plugins were disabled."));
|
||||||
|
else
|
||||||
|
QMessageBox::information(0, tr("Uninstall success"), tr("All selected plugins were uninstalled successfuly"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::enableSelection() {
|
||||||
|
QList<QTreeWidgetItem *> items = pluginsTree->selectedItems();
|
||||||
|
QTreeWidgetItem *item;
|
||||||
|
foreach(item, items) {
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
enginesEnabled.replace(index, QVariant(true));
|
||||||
|
item->setText(ENGINE_STATE, tr("True"));
|
||||||
|
setRowColor(index, "green");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::disableSelection() {
|
||||||
|
QList<QTreeWidgetItem *> items = pluginsTree->selectedItems();
|
||||||
|
QTreeWidgetItem *item;
|
||||||
|
foreach(item, items) {
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
enginesEnabled.replace(index, QVariant(false));
|
||||||
|
item->setText(ENGINE_STATE, tr("False"));
|
||||||
|
setRowColor(index, "red");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the color of a row in data model
|
||||||
|
void engineSelectDlg::setRowColor(int row, QString color){
|
||||||
|
QTreeWidgetItem *item = pluginsTree->topLevelItem(row);
|
||||||
|
for(int i=0; i<pluginsTree->columnCount(); ++i){
|
||||||
|
item->setData(i, Qt::ForegroundRole, QVariant(QColor(color)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::loadSupportedSearchEngines() {
|
||||||
|
// Some clean up first
|
||||||
|
pluginsTree->clear();
|
||||||
|
installed_engines.clear();
|
||||||
|
enginesEnabled.clear();
|
||||||
|
QStringList params;
|
||||||
|
// Ask nova core for the supported search engines
|
||||||
|
QProcess nova;
|
||||||
|
params << "--supported_engines";
|
||||||
|
nova.start(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py", params, QIODevice::ReadOnly);
|
||||||
|
nova.waitForStarted();
|
||||||
|
nova.waitForFinished();
|
||||||
|
QByteArray result = nova.readAll();
|
||||||
|
result = result.replace("\n", "");
|
||||||
|
qDebug("read: %s", result.data());
|
||||||
|
QByteArray e;
|
||||||
|
foreach(e, result.split(',')) {
|
||||||
|
QString en = QString(e);
|
||||||
|
installed_engines << en;
|
||||||
|
int index = known_engines.indexOf(en);
|
||||||
|
if(index == -1)
|
||||||
|
enginesEnabled << true;
|
||||||
|
else
|
||||||
|
enginesEnabled << known_enginesEnabled.at(index).toBool();
|
||||||
|
}
|
||||||
|
params.clear();
|
||||||
|
params << "--supported_engines_infos";
|
||||||
|
nova.start(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py", params, QIODevice::ReadOnly);
|
||||||
|
nova.waitForStarted();
|
||||||
|
nova.waitForFinished();
|
||||||
|
result = nova.readAll();
|
||||||
|
result = result.replace("\n", "");
|
||||||
|
qDebug("read: %s", result.data());
|
||||||
|
unsigned int i = 0;
|
||||||
|
foreach(e, result.split(',')) {
|
||||||
|
QString nameUrlCouple(e);
|
||||||
|
QStringList line = nameUrlCouple.split('|');
|
||||||
|
if(line.size() != 2) continue;
|
||||||
|
// Download favicon
|
||||||
|
QString enabledTxt;
|
||||||
|
if(enginesEnabled.at(i).toBool()){
|
||||||
|
enabledTxt = tr("True");
|
||||||
|
}else{
|
||||||
|
enabledTxt = tr("False");
|
||||||
|
}
|
||||||
|
line << enabledTxt;
|
||||||
|
QTreeWidgetItem *item = new QTreeWidgetItem(pluginsTree, line);
|
||||||
|
QString iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+installed_engines.at(i)+".png";
|
||||||
|
if(QFile::exists(iconPath)) {
|
||||||
|
// Good, we already have the icon
|
||||||
|
item->setData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath)));
|
||||||
|
} else {
|
||||||
|
// Icon is missing, we must download it
|
||||||
|
downloader->downloadUrl(line.at(1)+"/favicon.ico");
|
||||||
|
}
|
||||||
|
if(enginesEnabled.at(i).toBool())
|
||||||
|
setRowColor(i, "green");
|
||||||
|
else
|
||||||
|
setRowColor(i, "red");
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QTreeWidgetItem*> engineSelectDlg::findItemsWithUrl(QString url){
|
||||||
|
QList<QTreeWidgetItem*> res;
|
||||||
|
for(int i=0; i<pluginsTree->topLevelItemCount(); ++i) {
|
||||||
|
QTreeWidgetItem *item = pluginsTree->topLevelItem(i);
|
||||||
|
if(url.startsWith(item->text(ENGINE_URL)))
|
||||||
|
res << item;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool engineSelectDlg::isUpdateNeeded(QString plugin_name, float new_version) {
|
||||||
|
float old_version = misc::getPluginVersion(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+plugin_name+".py");
|
||||||
|
return (new_version > old_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::on_installButton_clicked() {
|
||||||
|
QStringList pathsList = QFileDialog::getOpenFileNames(0,
|
||||||
|
tr("Select search plugins"), QDir::homePath(),
|
||||||
|
tr("qBittorrent search plugins")+QString::fromUtf8(" (*.py)"));
|
||||||
|
QString path;
|
||||||
|
foreach(path, pathsList) {
|
||||||
|
if(!path.endsWith(".py")) continue;
|
||||||
|
float new_version = misc::getPluginVersion(path);
|
||||||
|
QString plugin_name = path.split(QDir::separator()).last();
|
||||||
|
plugin_name.replace(".py", "");
|
||||||
|
if(!isUpdateNeeded(plugin_name, new_version)) {
|
||||||
|
QMessageBox::information(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("A more recent version of %1 search engine plugin is already installed.", "%1 is the name of the search engine").arg(plugin_name.toUtf8().data()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Process with install
|
||||||
|
QString dest_path = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+plugin_name+".py";
|
||||||
|
bool update = false;
|
||||||
|
if(QFile::exists(dest_path)) {
|
||||||
|
QFile::remove(dest_path);
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
// Copy the plugin
|
||||||
|
QFile::copy(path, dest_path);
|
||||||
|
// Refresh plugin list
|
||||||
|
loadSupportedSearchEngines();
|
||||||
|
// TODO: do some more checking to be sure it was installed successfuly?
|
||||||
|
if(update) {
|
||||||
|
QMessageBox::information(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("%1 search engine plugin was successfuly updated.", "%1 is the name of the search engine").arg(plugin_name.toUtf8().data()));
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
QMessageBox::information(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("%1 search engine plugin was successfuly installed.", "%1 is the name of the search engine").arg(plugin_name.toUtf8().data()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool engineSelectDlg::parseVersionsFile(QString versions_file, QString updateServer) {
|
||||||
|
qDebug("Checking if update is needed");
|
||||||
|
bool file_correct = false;
|
||||||
|
QFile versions(versions_file);
|
||||||
|
if(!versions.open(QIODevice::ReadOnly | QIODevice::Text)){
|
||||||
|
qDebug("* Error: Could not read versions.txt file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while(!versions.atEnd()) {
|
||||||
|
QByteArray line = versions.readLine();
|
||||||
|
line.replace("\n", "");
|
||||||
|
line = line.trimmed();
|
||||||
|
if(line.isEmpty()) continue;
|
||||||
|
if(line.startsWith("#")) continue;
|
||||||
|
QList<QByteArray> list = line.split(' ');
|
||||||
|
if(list.size() != 2) continue;
|
||||||
|
QString plugin_name = QString(list.first());
|
||||||
|
if(!plugin_name.endsWith(":")) continue;
|
||||||
|
plugin_name.chop(1); // remove trailing ':'
|
||||||
|
bool ok;
|
||||||
|
float version = list.last().toFloat(&ok);
|
||||||
|
qDebug("read line %s: %.2f", plugin_name.toUtf8().data(), version);
|
||||||
|
if(!ok) continue;
|
||||||
|
file_correct = true;
|
||||||
|
if(isUpdateNeeded(plugin_name, version)) {
|
||||||
|
qDebug("Plugin: %s is outdated", plugin_name.toUtf8().data());
|
||||||
|
// Downloading update
|
||||||
|
downloader->downloadUrl(updateServer+plugin_name+".zip"); // Actually this is really a .py
|
||||||
|
downloader->downloadUrl(updateServer+plugin_name+".png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close file
|
||||||
|
versions.close();
|
||||||
|
// Clean up tmp file
|
||||||
|
QFile::remove(versions_file);
|
||||||
|
return file_correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::processDownloadedFile(QString url, QString filePath) {
|
||||||
|
if(url.endsWith("favicon.ico")){
|
||||||
|
// Icon downloaded
|
||||||
|
QImage fileIcon;
|
||||||
|
#ifdef HAVE_MAGICK
|
||||||
|
try{
|
||||||
|
QFile::copy(filePath, filePath+".ico");
|
||||||
|
Image image(QDir::cleanPath(filePath+".ico").toUtf8().data());
|
||||||
|
// Convert to PNG since we can't read ICO format
|
||||||
|
image.magick("PNG");
|
||||||
|
// Resize to 16x16px
|
||||||
|
image.sample(Geometry(16, 16));
|
||||||
|
image.write(filePath.toUtf8().data());
|
||||||
|
QFile::remove(filePath+".ico");
|
||||||
|
}catch(Magick::Exception &error_){
|
||||||
|
qDebug("favicon conversion to PNG failure: %s", error_.what());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(fileIcon.load(filePath)) {
|
||||||
|
QList<QTreeWidgetItem*> items = findItemsWithUrl(url);
|
||||||
|
QTreeWidgetItem *item;
|
||||||
|
foreach(item, items){
|
||||||
|
int index = pluginsTree->indexOfTopLevelItem(item);
|
||||||
|
Q_ASSERT(index != -1);
|
||||||
|
QString iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+installed_engines.at(index)+".png";
|
||||||
|
QFile::copy(filePath, iconPath);
|
||||||
|
item->setData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete tmp file
|
||||||
|
QFile::remove(filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(url == "http://www.dchris.eu/search_engine/versions.txt") {
|
||||||
|
if(!parseVersionsFile(filePath, "http://www.dchris.eu/search_engine/")) {
|
||||||
|
downloader->downloadUrl("http://hydr0g3n.free.fr/search_engine/versions.txt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(url == "http://hydr0g3n.free.fr/search_engine/versions.txt") {
|
||||||
|
if(!parseVersionsFile(filePath, "http://hydr0g3n.free.fr/search_engine/")) {
|
||||||
|
QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, update server is temporarily unavailable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(url.endsWith(".zip")) {
|
||||||
|
// a plugin update has been downloaded
|
||||||
|
QString plugin_name = url.split('/').last();
|
||||||
|
plugin_name.replace(".zip", "");
|
||||||
|
QString dest_path = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+plugin_name+".py";
|
||||||
|
bool new_plugin = false;
|
||||||
|
if(QFile::exists(dest_path)) {
|
||||||
|
// Delete the old plugin
|
||||||
|
QFile::remove(dest_path);
|
||||||
|
} else {
|
||||||
|
// This is a new plugin
|
||||||
|
new_plugin = true;
|
||||||
|
}
|
||||||
|
// Copy the new plugin
|
||||||
|
QFile::copy(filePath, dest_path);
|
||||||
|
if(new_plugin) {
|
||||||
|
// if it is new, refresh the list of plugins
|
||||||
|
loadSupportedSearchEngines();
|
||||||
|
}
|
||||||
|
QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("%1 search plugin was successfuly updated.", "%1 is the name of the search engine").arg(plugin_name.toUtf8().data()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void engineSelectDlg::handleDownloadFailure(QString url, QString reason) {
|
||||||
|
if(url.endsWith("favicon.ico")){
|
||||||
|
qDebug("Could not download favicon: %s, reason: %s", url.toUtf8().data(), reason.toUtf8().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(url == "http://www.dchris.eu/search_engine/versions.txt") {
|
||||||
|
// Primary update server failed, try secondary
|
||||||
|
qDebug("Primary update server failed, try secondary");
|
||||||
|
downloader->downloadUrl("http://hydr0g3n.free.fr/search_engine/versions.txt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(url == "http://hydr0g3n.free.fr/search_engine/versions.txt") {
|
||||||
|
QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, update server is temporarily unavailable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(url.endsWith(".zip")) {
|
||||||
|
// a plugin update download has been failed
|
||||||
|
QString plugin_name = url.split('/').last();
|
||||||
|
plugin_name.replace(".zip", "");
|
||||||
|
QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, %1 search plugin update failed.", "%1 is the name of the search engine").arg(plugin_name.toUtf8().data()));
|
||||||
|
}
|
||||||
|
}
|
69
src/engineSelectDlg.h
Normal file
69
src/engineSelectDlg.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ENGINE_SELECT_DLG_H
|
||||||
|
#define ENGINE_SELECT_DLG_H
|
||||||
|
|
||||||
|
#include "ui_engineSelect.h"
|
||||||
|
|
||||||
|
class downloadThread;
|
||||||
|
|
||||||
|
class engineSelectDlg : public QDialog, public Ui::engineSelect{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Search related
|
||||||
|
QStringList installed_engines;
|
||||||
|
QVariantList enginesEnabled;
|
||||||
|
QStringList known_engines;
|
||||||
|
QVariantList known_enginesEnabled;
|
||||||
|
downloadThread *downloader;
|
||||||
|
|
||||||
|
public:
|
||||||
|
engineSelectDlg(QWidget *parent);
|
||||||
|
~engineSelectDlg();
|
||||||
|
QList<QTreeWidgetItem*> findItemsWithUrl(QString url);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool parseVersionsFile(QString versions_file, QString updateServer);
|
||||||
|
bool isUpdateNeeded(QString plugin_name, float new_version);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void enginesChanged();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void loadSettings();
|
||||||
|
void saveSettings();
|
||||||
|
void on_closeButton_clicked();
|
||||||
|
void loadSupportedSearchEngines();
|
||||||
|
void toggleEngineState(QTreeWidgetItem*, int);
|
||||||
|
void setRowColor(int row, QString color);
|
||||||
|
void processDownloadedFile(QString url, QString filePath);
|
||||||
|
void handleDownloadFailure(QString url, QString reason);
|
||||||
|
void displayContextMenu(const QPoint& pos);
|
||||||
|
void enableSelection();
|
||||||
|
void disableSelection();
|
||||||
|
void on_actionUninstall_triggered();
|
||||||
|
void on_updateButton_clicked();
|
||||||
|
void on_installButton_clicked();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
22
src/misc.h
22
src/misc.h
|
@ -328,6 +328,28 @@ class misc : public QObject{
|
||||||
list.insert(i, value);
|
list.insert(i, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float getPluginVersion(QString filePath) {
|
||||||
|
QFile plugin(filePath);
|
||||||
|
if(!plugin.exists()){
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if(!plugin.open(QIODevice::ReadOnly | QIODevice::Text)){
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
float version = 0.0;
|
||||||
|
while (!plugin.atEnd()){
|
||||||
|
QByteArray line = plugin.readLine();
|
||||||
|
if(line.startsWith("#VERSION: ")){
|
||||||
|
line = line.split(' ').last();
|
||||||
|
line.replace("\n", "");
|
||||||
|
version = line.toFloat();
|
||||||
|
qDebug("plugin version: %.2f", version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
// Take a number of seconds and return an user-friendly
|
// Take a number of seconds and return an user-friendly
|
||||||
// time duration like "1d 2h 10m".
|
// time duration like "1d 2h 10m".
|
||||||
static QString userFriendlyDuration(qlonglong seconds) {
|
static QString userFriendlyDuration(qlonglong seconds) {
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
<!DOCTYPE RCC><RCC version="1.0">
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
<qresource>
|
<qresource>
|
||||||
<file>search_engine/nova.py</file>
|
<file>search_engine/nova2.py</file>
|
||||||
|
<file>search_engine/novaprinter.py</file>
|
||||||
|
<file>search_engine/engines/isohunt.py</file>
|
||||||
|
<file>search_engine/engines/btjunkie.py</file>
|
||||||
|
<file>search_engine/engines/torrentreactor.py</file>
|
||||||
|
<file>search_engine/engines/mininova.py</file>
|
||||||
|
<file>search_engine/engines/piratebay.py</file>
|
||||||
|
<file>search_engine/engines/torrentreactor.png</file>
|
||||||
|
<file>search_engine/engines/mininova.png</file>
|
||||||
|
<file>search_engine/engines/piratebay.png</file>
|
||||||
|
<file>search_engine/engines/btjunkie.png</file>
|
||||||
|
<file>search_engine/engines/isohunt.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
160
src/search.ui
160
src/search.ui
|
@ -13,93 +13,22 @@
|
||||||
<string>Search</string>
|
<string>Search</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" >
|
<layout class="QVBoxLayout" >
|
||||||
<property name="margin" >
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" >
|
<layout class="QHBoxLayout" >
|
||||||
<property name="margin" >
|
<property name="spacing" >
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin" >
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing" >
|
<property name="topMargin" >
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupEngines" >
|
|
||||||
<property name="minimumSize" >
|
|
||||||
<size>
|
|
||||||
<width>131</width>
|
|
||||||
<height>132</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize" >
|
|
||||||
<size>
|
|
||||||
<width>125</width>
|
|
||||||
<height>132</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="title" >
|
|
||||||
<string>Search Engines</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" >
|
|
||||||
<property name="margin" >
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="mininova" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="checked" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="piratebay" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="isohunt" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="reactor" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" >
|
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing" >
|
<property name="rightMargin" >
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" >
|
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing" >
|
<property name="bottomMargin" >
|
||||||
<number>6</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="search_lbl" >
|
<widget class="QLabel" name="search_lbl" >
|
||||||
|
@ -164,16 +93,17 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="enginesButton" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Search engines...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" >
|
<layout class="QHBoxLayout" >
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="status_lbl" >
|
<widget class="QLabel" name="status_lbl" >
|
||||||
<property name="maximumSize" >
|
<property name="maximumSize" >
|
||||||
|
@ -235,34 +165,48 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" >
|
<property name="sizeHint" >
|
||||||
<size>
|
<size>
|
||||||
<width>40</width>
|
<width>188</width>
|
||||||
<height>20</height>
|
<height>21</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" >
|
<layout class="QVBoxLayout" >
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
<number>6</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="leftMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" >
|
<layout class="QHBoxLayout" >
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
<number>6</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="leftMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="results_lbl" >
|
<widget class="QLabel" name="results_lbl" >
|
||||||
<property name="maximumSize" >
|
<property name="maximumSize" >
|
||||||
|
@ -329,12 +273,21 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" >
|
<layout class="QHBoxLayout" >
|
||||||
<property name="margin" >
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
<number>6</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="leftMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="download_button" >
|
<widget class="QPushButton" name="download_button" >
|
||||||
<property name="enabled" >
|
<property name="enabled" >
|
||||||
|
@ -368,13 +321,6 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="update_nova_button" >
|
|
||||||
<property name="text" >
|
|
||||||
<string>Update search plugin</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -45,8 +45,6 @@
|
||||||
SearchEngine::SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration) : QWidget(), BTSession(BTSession), myTrayIcon(myTrayIcon), systrayIntegration(systrayIntegration){
|
SearchEngine::SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration) : QWidget(), BTSession(BTSession), myTrayIcon(myTrayIcon), systrayIntegration(systrayIntegration){
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
downloader = new downloadThread(this);
|
downloader = new downloadThread(this);
|
||||||
connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(novaUpdateDownloaded(QString, QString)));
|
|
||||||
connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleNovaDownloadFailure(QString, QString)));
|
|
||||||
// Set Search results list model
|
// Set Search results list model
|
||||||
SearchListModel = new QStandardItemModel(0,5);
|
SearchListModel = new QStandardItemModel(0,5);
|
||||||
SearchListModel->setHeaderData(SEARCH_NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
|
SearchListModel->setHeaderData(SEARCH_NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
|
||||||
|
@ -81,21 +79,8 @@ SearchEngine::SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, b
|
||||||
connect(searchProcess, SIGNAL(started()), this, SLOT(searchStarted()));
|
connect(searchProcess, SIGNAL(started()), this, SLOT(searchStarted()));
|
||||||
connect(searchProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readSearchOutput()));
|
connect(searchProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readSearchOutput()));
|
||||||
connect(searchProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(searchFinished(int,QProcess::ExitStatus)));
|
connect(searchProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(searchFinished(int,QProcess::ExitStatus)));
|
||||||
// Set search engines names
|
// Check last enabled search engines
|
||||||
mininova->setText("Mininova");
|
loadEngineSettings();
|
||||||
piratebay->setText("ThePirateBay");
|
|
||||||
// reactor->setText("TorrentReactor");
|
|
||||||
isohunt->setText("Isohunt");
|
|
||||||
// btjunkie->setText("BTJunkie");
|
|
||||||
reactor->setText("TorrentReactor");
|
|
||||||
// Check last checked search engines
|
|
||||||
loadCheckedSearchEngines();
|
|
||||||
connect(mininova, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
connect(piratebay, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
// connect(reactor, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
connect(isohunt, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
// connect(btjunkie, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
connect(reactor, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int)));
|
|
||||||
// Update nova.py search plugin if necessary
|
// Update nova.py search plugin if necessary
|
||||||
updateNova();
|
updateNova();
|
||||||
}
|
}
|
||||||
|
@ -183,18 +168,6 @@ void SearchEngine::sortSearchListString(int index, Qt::SortOrder sortOrder){
|
||||||
SearchListModel->removeRows(0, nbRows_old);
|
SearchListModel->removeRows(0, nbRows_old);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save last checked search engines to a file
|
|
||||||
void SearchEngine::saveCheckedSearchEngines(int) const{
|
|
||||||
QSettings settings("qBittorrent", "qBittorrent");
|
|
||||||
settings.beginGroup("SearchEngines");
|
|
||||||
settings.setValue("mininova", mininova->isChecked());
|
|
||||||
settings.setValue("piratebay", piratebay->isChecked());
|
|
||||||
settings.setValue("isohunt", isohunt->isChecked());
|
|
||||||
settings.setValue("reactor", reactor->isChecked());
|
|
||||||
settings.endGroup();
|
|
||||||
qDebug("Saved checked search engines");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save columns width in a file to remember them
|
// Save columns width in a file to remember them
|
||||||
// (download list)
|
// (download list)
|
||||||
void SearchEngine::saveColWidthSearchList() const{
|
void SearchEngine::saveColWidthSearchList() const{
|
||||||
|
@ -208,6 +181,11 @@ void SearchEngine::saveColWidthSearchList() const{
|
||||||
qDebug("Search list columns width saved");
|
qDebug("Search list columns width saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchEngine::on_enginesButton_clicked() {
|
||||||
|
engineSelectDlg *dlg = new engineSelectDlg(this);
|
||||||
|
connect(dlg, SIGNAL(enginesChanged()), this, SLOT(loadEngineSettings()));
|
||||||
|
}
|
||||||
|
|
||||||
// Load columns width in a file that were saved previously
|
// Load columns width in a file that were saved previously
|
||||||
// (search list)
|
// (search list)
|
||||||
bool SearchEngine::loadColWidthSearchList(){
|
bool SearchEngine::loadColWidthSearchList(){
|
||||||
|
@ -226,19 +204,6 @@ bool SearchEngine::loadColWidthSearchList(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load last checked search engines from a file
|
|
||||||
void SearchEngine::loadCheckedSearchEngines(){
|
|
||||||
qDebug("Loading checked search engines");
|
|
||||||
QSettings settings("qBittorrent", "qBittorrent");
|
|
||||||
settings.beginGroup("SearchEngines");
|
|
||||||
mininova->setChecked(settings.value("mininova", true).toBool());
|
|
||||||
piratebay->setChecked(settings.value("piratebay", false).toBool());
|
|
||||||
isohunt->setChecked(settings.value("isohunt", false).toBool());
|
|
||||||
reactor->setChecked(settings.value("reactor", false).toBool());
|
|
||||||
settings.endGroup();
|
|
||||||
qDebug("Loaded checked search engines");
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the last searchs from a QSettings to a QStringList
|
// get the last searchs from a QSettings to a QStringList
|
||||||
void SearchEngine::startSearchHistory(){
|
void SearchEngine::startSearchHistory(){
|
||||||
QSettings settings("qBittorrent", "qBittorrent");
|
QSettings settings("qBittorrent", "qBittorrent");
|
||||||
|
@ -247,6 +212,24 @@ void SearchEngine::startSearchHistory(){
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchEngine::loadEngineSettings() {
|
||||||
|
qDebug("Loading engine settings");
|
||||||
|
enabled_engines.clear();
|
||||||
|
QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent"));
|
||||||
|
QStringList known_engines = settings.value(QString::fromUtf8("SearchEngines/knownEngines"), QStringList()).toStringList();
|
||||||
|
QVariantList known_enginesEnabled = settings.value(QString::fromUtf8("SearchEngines/knownEnginesEnabled"), QList<QVariant>()).toList();
|
||||||
|
QString engine;
|
||||||
|
unsigned int i = 0;
|
||||||
|
foreach(engine, known_engines) {
|
||||||
|
if(known_enginesEnabled.at(i).toBool())
|
||||||
|
enabled_engines << engine;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if(enabled_engines.empty())
|
||||||
|
enabled_engines << "all";
|
||||||
|
qDebug("Engine settings loaded");
|
||||||
|
}
|
||||||
|
|
||||||
// Save the history list into the QSettings for the next session
|
// Save the history list into the QSettings for the next session
|
||||||
void SearchEngine::saveSearchHistory()
|
void SearchEngine::saveSearchHistory()
|
||||||
{
|
{
|
||||||
|
@ -282,33 +265,12 @@ void SearchEngine::on_search_button_clicked(){
|
||||||
|
|
||||||
|
|
||||||
// Getting checked search engines
|
// Getting checked search engines
|
||||||
if(!mininova->isChecked() && ! piratebay->isChecked() && !reactor->isChecked() && !isohunt->isChecked()/* && !btjunkie->isChecked()*/ /*&& !meganova->isChecked()*/){
|
Q_ASSERT(!enabled_engines.empty());
|
||||||
QMessageBox::critical(0, tr("No search engine selected"), tr("You must select at least one search engine."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QStringList params;
|
QStringList params;
|
||||||
QStringList engineNames;
|
QStringList engineNames;
|
||||||
search_stopped = false;
|
search_stopped = false;
|
||||||
// Get checked search engines
|
|
||||||
if(mininova->isChecked()){
|
params << enabled_engines.join(",");
|
||||||
engineNames << "mininova";
|
|
||||||
}
|
|
||||||
if(piratebay->isChecked()){
|
|
||||||
engineNames << "piratebay";
|
|
||||||
}
|
|
||||||
// if(reactor->isChecked()){
|
|
||||||
// engineNames << "reactor";
|
|
||||||
// }
|
|
||||||
if(isohunt->isChecked()){
|
|
||||||
engineNames << "isohunt";
|
|
||||||
}
|
|
||||||
// if(btjunkie->isChecked()){
|
|
||||||
// engineNames << "btjunkie";
|
|
||||||
// }
|
|
||||||
if(reactor->isChecked()){
|
|
||||||
engineNames << "reactor";
|
|
||||||
}
|
|
||||||
params << engineNames.join(",");
|
|
||||||
params << pattern.split(" ");
|
params << pattern.split(" ");
|
||||||
// Update SearchEngine widgets
|
// Update SearchEngine widgets
|
||||||
no_search_results = true;
|
no_search_results = true;
|
||||||
|
@ -316,7 +278,7 @@ void SearchEngine::on_search_button_clicked(){
|
||||||
search_result_line_truncated.clear();
|
search_result_line_truncated.clear();
|
||||||
results_lbl->setText(tr("Results")+" <i>(0)</i>:");
|
results_lbl->setText(tr("Results")+" <i>(0)</i>:");
|
||||||
// Launch search
|
// Launch search
|
||||||
searchProcess->start(misc::qBittorrentPath()+"nova.py", params, QIODevice::ReadOnly);
|
searchProcess->start(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py", params, QIODevice::ReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchEngine::searchStarted(){
|
void SearchEngine::searchStarted(){
|
||||||
|
@ -360,137 +322,105 @@ void SearchEngine::readSearchOutput(){
|
||||||
results_lbl->setText(tr("Results")+QString::fromUtf8(" <i>(")+misc::toQString(nb_search_results)+QString::fromUtf8(")</i>:"));
|
results_lbl->setText(tr("Results")+QString::fromUtf8(" <i>(")+misc::toQString(nb_search_results)+QString::fromUtf8(")</i>:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns version of nova.py search engine
|
|
||||||
float SearchEngine::getNovaVersion(QString novaPath) const{
|
|
||||||
QFile dest_nova(novaPath);
|
|
||||||
if(!dest_nova.exists()){
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
float version = 0.0;
|
|
||||||
while (!dest_nova.atEnd()){
|
|
||||||
QByteArray line = dest_nova.readLine();
|
|
||||||
if(line.startsWith("# Version: ")){
|
|
||||||
line = line.split(' ').last();
|
|
||||||
line.chop(1); // removes '\n'
|
|
||||||
version = line.toFloat();
|
|
||||||
qDebug("Search plugin version: %.2f", version);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns changelog of nova.py search engine
|
|
||||||
QByteArray SearchEngine::getNovaChangelog(QString novaPath, float my_version) const{
|
|
||||||
QFile dest_nova(novaPath);
|
|
||||||
if(!dest_nova.exists()){
|
|
||||||
return QByteArray("None");
|
|
||||||
}
|
|
||||||
if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){
|
|
||||||
return QByteArray("None");
|
|
||||||
}
|
|
||||||
QByteArray changelog;
|
|
||||||
bool in_changelog = false;
|
|
||||||
while (!dest_nova.atEnd()){
|
|
||||||
QByteArray line = dest_nova.readLine();
|
|
||||||
line = line.trimmed();
|
|
||||||
if(line.startsWith("# Changelog:")){
|
|
||||||
in_changelog = true;
|
|
||||||
}else{
|
|
||||||
if(line.isEmpty()){
|
|
||||||
in_changelog = false;
|
|
||||||
}
|
|
||||||
if(line.startsWith("# End Changelog")) break;
|
|
||||||
QString end_version = "# Version: ";
|
|
||||||
char tmp[5];
|
|
||||||
snprintf(tmp, 5, "%.2f", my_version);
|
|
||||||
end_version+=QString::fromUtf8(tmp);
|
|
||||||
if(line.startsWith((const char*)end_version.toUtf8())) break;
|
|
||||||
if(in_changelog){
|
|
||||||
line.remove(0,1);
|
|
||||||
line += "\n";
|
|
||||||
changelog.append(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changelog;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update nova.py search plugin if necessary
|
// Update nova.py search plugin if necessary
|
||||||
void SearchEngine::updateNova() const{
|
void SearchEngine::updateNova() {
|
||||||
qDebug("Updating nova");
|
qDebug("Updating nova");
|
||||||
float provided_nova_version = getNovaVersion(":/search_engine/nova.py");
|
// create search_engine directory if necessary
|
||||||
QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup;
|
QDir search_dir(misc::qBittorrentPath()+"search_engine");
|
||||||
QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm);
|
if(!search_dir.exists()){
|
||||||
if(provided_nova_version > getNovaVersion(misc::qBittorrentPath()+"nova.py")){
|
search_dir.mkdir(misc::qBittorrentPath()+"search_engine");
|
||||||
qDebug("updating local search plugin with shipped one");
|
|
||||||
// nova.py needs update
|
|
||||||
QFile::remove(misc::qBittorrentPath()+"nova.py");
|
|
||||||
qDebug("Old nova removed");
|
|
||||||
QFile::copy(":/search_engine/nova.py", misc::qBittorrentPath()+"nova.py");
|
|
||||||
qDebug("New nova copied");
|
|
||||||
QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup;
|
|
||||||
QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm);
|
|
||||||
qDebug("local search plugin updated");
|
|
||||||
}
|
}
|
||||||
|
QFile package_file(search_dir.path()+QDir::separator()+"__init__.py");
|
||||||
|
package_file.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
|
package_file.close();
|
||||||
|
if(!search_dir.exists("engines")){
|
||||||
|
search_dir.mkdir("engines");
|
||||||
}
|
}
|
||||||
|
QFile package_file2(search_dir.path()+QDir::separator()+"engines"+QDir::separator()+"__init__.py");
|
||||||
void SearchEngine::novaUpdateDownloaded(QString url, QString filePath){
|
package_file2.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
float version_on_server = getNovaVersion(filePath);
|
package_file2.close();
|
||||||
qDebug("Version on qbittorrent.org: %.2f", version_on_server);
|
// Copy search plugin files (if necessary)
|
||||||
float my_version = getNovaVersion(misc::qBittorrentPath()+"nova.py");
|
QString filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py";
|
||||||
if(version_on_server > my_version){
|
if(misc::getPluginVersion(":/search_engine/nova2.py") > misc::getPluginVersion(filePath)) {
|
||||||
if(QMessageBox::question(this,
|
if(QFile::exists(filePath))
|
||||||
tr("Search plugin update -- qBittorrent"),
|
|
||||||
tr("Search plugin can be updated, do you want to update it?\n\nChangelog:\n")+getNovaChangelog(filePath, my_version),
|
|
||||||
tr("&Yes"), tr("&No"),
|
|
||||||
QString(), 0, 1)){
|
|
||||||
return;
|
|
||||||
}else{
|
|
||||||
qDebug("Updating search plugin from qbittorrent.org");
|
|
||||||
QFile::remove(misc::qBittorrentPath()+"nova.py");
|
|
||||||
QFile::copy(filePath, misc::qBittorrentPath()+"nova.py");
|
|
||||||
QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup;
|
|
||||||
QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(version_on_server == 0.0){
|
|
||||||
if(url == "http://www.dchris.eu/nova/nova.zip"){
|
|
||||||
qDebug("*Warning: Search plugin update download from primary server failed, trying secondary server...");
|
|
||||||
downloader->downloadUrl("http://hydr0g3n.free.fr/nova/nova.py");
|
|
||||||
}else{
|
|
||||||
QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"),
|
|
||||||
tr("Sorry, update server is temporarily unavailable."));
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
QMessageBox::information(this, tr("Search plugin update -- qBittorrent"),
|
|
||||||
tr("Your search plugin is already up to date."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete tmp file
|
|
||||||
QFile::remove(filePath);
|
QFile::remove(filePath);
|
||||||
|
QFile::copy(":/search_engine/nova2.py", misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py");
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
if(!QFile::exists(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"novaprinter.py")){
|
||||||
|
QFile::copy(":/search_engine/novaprinter.py", misc::qBittorrentPath()+"search_engine"+QDir::separator()+"novaprinter.py");
|
||||||
|
}
|
||||||
|
QString subDir = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator();
|
||||||
|
QDir search_subDir(":/search_engine/engines");
|
||||||
|
QStringList files = search_subDir.entryList();
|
||||||
|
QString file;
|
||||||
|
foreach(file, files){
|
||||||
|
filePath = search_subDir.path()+QDir::separator()+file;
|
||||||
|
// Copy python classes
|
||||||
|
if(file.endsWith(".py")) {
|
||||||
|
if(misc::getPluginVersion(filePath) > misc::getPluginVersion(subDir+file) ) {
|
||||||
|
if(QFile::exists(filePath))
|
||||||
|
QFile::remove(filePath);
|
||||||
|
QFile::copy(filePath, subDir+file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchEngine::handleNovaDownloadFailure(QString url, QString reason){
|
|
||||||
if(url == "http://www.dchris.eu/nova/nova.zip"){
|
|
||||||
qDebug("*Warning: Search plugin update download from primary server failed, trying secondary server...");
|
|
||||||
downloader->downloadUrl("http://hydr0g3n.free.fr/nova/nova.py");
|
|
||||||
} else {
|
} else {
|
||||||
// Display a message box
|
// Copy icons
|
||||||
QMessageBox::critical(0, tr("Search plugin download error"), tr("Couldn't download search plugin update at url: %1, reason: %2.").arg(url).arg(reason));
|
if(file.endsWith(".png")) {
|
||||||
|
if(!QFile::exists(subDir+file)) {
|
||||||
|
QFile::copy(filePath, subDir+file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download nova.py from qbittorrent.org
|
// void SearchEngine::novaUpdateDownloaded(QString url, QString filePath){
|
||||||
// Check if our nova.py is outdated and
|
// float version_on_server = getNovaVersion(filePath);
|
||||||
// ask user for action.
|
// qDebug("Version on qbittorrent.org: %.2f", version_on_server);
|
||||||
void SearchEngine::on_update_nova_button_clicked(){
|
// float my_version = getNovaVersion(misc::qBittorrentPath()+"nova.py");
|
||||||
qDebug("Checking for search plugin updates on qbittorrent.org");
|
// if(version_on_server > my_version){
|
||||||
downloader->downloadUrl("http://www.dchris.eu/nova/nova.zip");
|
// if(QMessageBox::question(this,
|
||||||
}
|
// tr("Search plugin update -- qBittorrent"),
|
||||||
|
// tr("Search plugin can be updated, do you want to update it?\n\nChangelog:\n")+getNovaChangelog(filePath, my_version),
|
||||||
|
// tr("&Yes"), tr("&No"),
|
||||||
|
// QString(), 0, 1)){
|
||||||
|
// return;
|
||||||
|
// }else{
|
||||||
|
// qDebug("Updating search plugin from qbittorrent.org");
|
||||||
|
// QFile::remove(misc::qBittorrentPath()+"nova.py");
|
||||||
|
// QFile::copy(filePath, misc::qBittorrentPath()+"nova.py");
|
||||||
|
// QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup;
|
||||||
|
// QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm);
|
||||||
|
// }
|
||||||
|
// }else{
|
||||||
|
// if(version_on_server == 0.0){
|
||||||
|
// if(url == "http://www.dchris.eu/nova/nova.zip"){
|
||||||
|
// qDebug("*Warning: Search plugin update download from primary server failed, trying secondary server...");
|
||||||
|
// downloader->downloadUrl("http://hydr0g3n.free.fr/nova/nova.py");
|
||||||
|
// }else{
|
||||||
|
// QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"),
|
||||||
|
// tr("Sorry, update server is temporarily unavailable."));
|
||||||
|
// }
|
||||||
|
// }else{
|
||||||
|
// QMessageBox::information(this, tr("Search plugin update -- qBittorrent"),
|
||||||
|
// tr("Your search plugin is already up to date."));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // Delete tmp file
|
||||||
|
// QFile::remove(filePath);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void SearchEngine::handleNovaDownloadFailure(QString url, QString reason){
|
||||||
|
// if(url == "http://www.dchris.eu/nova/nova.zip"){
|
||||||
|
// qDebug("*Warning: Search plugin update download from primary server failed, trying secondary server...");
|
||||||
|
// downloader->downloadUrl("http://hydr0g3n.free.fr/nova/nova.py");
|
||||||
|
// }else{
|
||||||
|
// // Display a message box
|
||||||
|
// QMessageBox::critical(0, tr("Search plugin download error"), tr("Couldn't download search plugin update at url: %1, reason: %2.").arg(url).arg(reason));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Slot called when search is Finished
|
// Slot called when search is Finished
|
||||||
// Search can be finished for 3 reasons :
|
// Search can be finished for 3 reasons :
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "ui_search.h"
|
#include "ui_search.h"
|
||||||
|
#include "engineSelectDlg.h"
|
||||||
|
|
||||||
class QStandardItemModel;
|
class QStandardItemModel;
|
||||||
class SearchListDelegate;
|
class SearchListDelegate;
|
||||||
|
@ -52,21 +53,20 @@ class SearchEngine : public QWidget, public Ui::search_engine{
|
||||||
QSystemTrayIcon *myTrayIcon;
|
QSystemTrayIcon *myTrayIcon;
|
||||||
bool systrayIntegration;
|
bool systrayIntegration;
|
||||||
downloadThread *downloader;
|
downloadThread *downloader;
|
||||||
|
QStringList enabled_engines;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration);
|
SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration);
|
||||||
~SearchEngine();
|
~SearchEngine();
|
||||||
float getNovaVersion(QString novaPath) const;
|
float getPluginVersion(QString filePath) const;
|
||||||
QByteArray getNovaChangelog(QString novaPath, float my_version) const;
|
|
||||||
bool loadColWidthSearchList();
|
bool loadColWidthSearchList();
|
||||||
|
|
||||||
public slots:
|
protected slots:
|
||||||
// Search slots
|
// Search slots
|
||||||
void on_search_button_clicked();
|
void on_search_button_clicked();
|
||||||
void on_stop_search_button_clicked();
|
void on_stop_search_button_clicked();
|
||||||
void on_clear_button_clicked();
|
void on_clear_button_clicked();
|
||||||
void on_download_button_clicked();
|
void on_download_button_clicked();
|
||||||
void on_update_nova_button_clicked();
|
|
||||||
void appendSearchResult(QString line);
|
void appendSearchResult(QString line);
|
||||||
void searchFinished(int exitcode,QProcess::ExitStatus);
|
void searchFinished(int exitcode,QProcess::ExitStatus);
|
||||||
void readSearchOutput();
|
void readSearchOutput();
|
||||||
|
@ -74,16 +74,14 @@ class SearchEngine : public QWidget, public Ui::search_engine{
|
||||||
void searchStarted();
|
void searchStarted();
|
||||||
void downloadSelectedItem(const QModelIndex& index);
|
void downloadSelectedItem(const QModelIndex& index);
|
||||||
void startSearchHistory();
|
void startSearchHistory();
|
||||||
void loadCheckedSearchEngines();
|
void updateNova();
|
||||||
void updateNova() const;
|
|
||||||
void saveSearchHistory();
|
void saveSearchHistory();
|
||||||
void saveColWidthSearchList() const;
|
void saveColWidthSearchList() const;
|
||||||
void saveCheckedSearchEngines(int) const;
|
|
||||||
void sortSearchList(int index);
|
void sortSearchList(int index);
|
||||||
void sortSearchListInt(int index, Qt::SortOrder sortOrder);
|
void sortSearchListInt(int index, Qt::SortOrder sortOrder);
|
||||||
void sortSearchListString(int index, Qt::SortOrder sortOrder);
|
void sortSearchListString(int index, Qt::SortOrder sortOrder);
|
||||||
void novaUpdateDownloaded(QString url, QString path);
|
void on_enginesButton_clicked();
|
||||||
void handleNovaDownloadFailure(QString url, QString reason);
|
void loadEngineSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
0
src/search_engine/__init__.py
Normal file
0
src/search_engine/__init__.py
Normal file
0
src/search_engine/engines/__init__.py
Normal file
0
src/search_engine/engines/__init__.py
Normal file
BIN
src/search_engine/engines/btjunkie.png
Normal file
BIN
src/search_engine/engines/btjunkie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 B |
29
src/search_engine/engines/btjunkie.py
Normal file
29
src/search_engine/engines/btjunkie.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#VERSION: 1.00
|
||||||
|
#AUTHORS: Fabien Devaux (fab@gnux.info)
|
||||||
|
from novaprinter import prettyPrinter
|
||||||
|
import urllib
|
||||||
|
import re
|
||||||
|
|
||||||
|
# TODO: add multipage
|
||||||
|
class btjunkie(object):
|
||||||
|
url = 'http://btjunkie.org'
|
||||||
|
name = 'btjunkie'
|
||||||
|
|
||||||
|
def search(self, what):
|
||||||
|
dat = urllib.urlopen(self.url+'/search?q=%s'%what).read().decode('utf8', 'replace')
|
||||||
|
# I know it's not very readable, but the SGML parser feels in pain
|
||||||
|
section_re = re.compile('(?s)href="/torrent\?do=download.*?<tr>')
|
||||||
|
torrent_re = re.compile('(?s)href="(?P<link>.*?do=download[^"]+).*?'
|
||||||
|
'class="BlckUnd">(?P<name>.*?)</a>.*?'
|
||||||
|
'>(?P<size>\d+MB)</font>.*?'
|
||||||
|
'>(?P<seeds>\d+)</font>.*?'
|
||||||
|
'>(?P<leech>\d+)</font>')
|
||||||
|
for match in section_re.finditer(dat):
|
||||||
|
txt = match.group(0)
|
||||||
|
m = torrent_re.search(txt)
|
||||||
|
if m:
|
||||||
|
torrent_infos = m.groupdict()
|
||||||
|
torrent_infos['name'] = re.sub('</?font.*?>', '', torrent_infos['name'])
|
||||||
|
torrent_infos['engine_url'] = self.url
|
||||||
|
torrent_infos['link'] = self.url+torrent_infos['link']
|
||||||
|
prettyPrinter(torrent_infos)
|
BIN
src/search_engine/engines/isohunt.png
Normal file
BIN
src/search_engine/engines/isohunt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 633 B |
78
src/search_engine/engines/isohunt.py
Normal file
78
src/search_engine/engines/isohunt.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#VERSION: 1.00
|
||||||
|
#AUTHORS: Gekko Dam Beer (gekko04@users.sourceforge.net)
|
||||||
|
from novaprinter import prettyPrinter
|
||||||
|
import sgmllib
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
class isohunt(object):
|
||||||
|
url = 'http://isohunt.com'
|
||||||
|
name = 'isoHunt'
|
||||||
|
|
||||||
|
class SimpleSGMLParser(sgmllib.SGMLParser):
|
||||||
|
def __init__(self, results, url, *args):
|
||||||
|
sgmllib.SGMLParser.__init__(self)
|
||||||
|
self.td_counter = None
|
||||||
|
self.current_item = None
|
||||||
|
self.results = results
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def start_tr(self, attr):
|
||||||
|
params = dict(attr)
|
||||||
|
if 'onclick' in params:
|
||||||
|
Durl='http://isohunt.com/download'
|
||||||
|
self.current_item = {}
|
||||||
|
self.td_counter = 0
|
||||||
|
try:
|
||||||
|
self.current_item['link'] = '%s/%s'%(Durl, params['onclick'].split('/')[2])
|
||||||
|
except IndexError:
|
||||||
|
self.current_item['link'] = None
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
if self.td_counter == 3:
|
||||||
|
if not self.current_item.has_key('name'):
|
||||||
|
self.current_item['name'] = ''
|
||||||
|
self.current_item['name']+= data.strip()
|
||||||
|
if self.td_counter == 4:
|
||||||
|
if not self.current_item.has_key('size'):
|
||||||
|
self.current_item['size'] = ''
|
||||||
|
self.current_item['size']+= data.strip()
|
||||||
|
if self.td_counter == 5:
|
||||||
|
if not self.current_item.has_key('seeds'):
|
||||||
|
self.current_item['seeds'] = ''
|
||||||
|
self.current_item['seeds']+= data.strip()
|
||||||
|
if self.td_counter == 6:
|
||||||
|
if not self.current_item.has_key('leech'):
|
||||||
|
self.current_item['leech'] = ''
|
||||||
|
self.current_item['leech']+= data.strip()
|
||||||
|
|
||||||
|
def start_td(self,attr):
|
||||||
|
if isinstance(self.td_counter,int):
|
||||||
|
self.td_counter += 1
|
||||||
|
if self.td_counter > 7:
|
||||||
|
self.td_counter = None
|
||||||
|
# add item to results
|
||||||
|
if self.current_item:
|
||||||
|
self.current_item['engine_url'] = self.url
|
||||||
|
if not self.current_item.has_key('seeds') or not self.current_item['seeds'].isdigit():
|
||||||
|
self.current_item['seeds'] = 0
|
||||||
|
if not self.current_item.has_key('leech') or not self.current_item['leech'].isdigit():
|
||||||
|
self.current_item['leech'] = 0
|
||||||
|
if self.current_item['link'] is not None:
|
||||||
|
prettyPrinter(self.current_item)
|
||||||
|
self.results.append('a')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.results = []
|
||||||
|
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
||||||
|
|
||||||
|
def search(self, what):
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
results = []
|
||||||
|
parser = self.SimpleSGMLParser(results, self.url)
|
||||||
|
dat = urllib.urlopen(self.url+'/torrents.php?ihq=%s&ihp=%s'%(what,i)).read().decode('utf-8', 'replace')
|
||||||
|
parser.feed(dat)
|
||||||
|
parser.close()
|
||||||
|
if len(results) <= 0:
|
||||||
|
break
|
||||||
|
i += 1
|
BIN
src/search_engine/engines/mininova.png
Normal file
BIN
src/search_engine/engines/mininova.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 365 B |
50
src/search_engine/engines/mininova.py
Normal file
50
src/search_engine/engines/mininova.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#VERSION: 1.00
|
||||||
|
#AUTHORS: Fabien Devaux (fab@gnux.info)
|
||||||
|
from novaprinter import prettyPrinter
|
||||||
|
import urllib
|
||||||
|
from xml.dom import minidom
|
||||||
|
import re
|
||||||
|
|
||||||
|
class mininova(object):
|
||||||
|
url = 'http://www.mininova.org'
|
||||||
|
name = 'Mininova'
|
||||||
|
table_items = 'added cat name size seeds leech'.split()
|
||||||
|
|
||||||
|
def search(self, what):
|
||||||
|
order = 'seeds' # must be one in self.table_items
|
||||||
|
|
||||||
|
def get_link(lnk):
|
||||||
|
lnks = lnk.getElementsByTagName('a')
|
||||||
|
if lnks.item(0).attributes.get('href').value.startswith('/faq'):
|
||||||
|
if len(lnks) > 1:
|
||||||
|
return self.url+lnks.item(1).attributes.get('href').value
|
||||||
|
else:
|
||||||
|
return self.url+lnks.item(0).attributes.get('href').value
|
||||||
|
|
||||||
|
def get_text(txt):
|
||||||
|
if txt.nodeType == txt.TEXT_NODE:
|
||||||
|
return txt.toxml()
|
||||||
|
else:
|
||||||
|
return ''.join([ get_text(n) for n in txt.childNodes])
|
||||||
|
dat = urllib.urlopen(self.url+'/search/%s/seeds'%(what,)).read().decode('utf-8', 'replace')
|
||||||
|
dat = re.sub("<a href=\"http://www.boardreader.com/index.php.*\"", "<a href=\"plop\"", dat)
|
||||||
|
dat = re.sub("<=", "<=", dat)
|
||||||
|
x = minidom.parseString(dat.encode('utf-8', 'replace'))
|
||||||
|
table = x.getElementsByTagName('table').item(0)
|
||||||
|
if not table: return
|
||||||
|
for tr in table.getElementsByTagName('tr'):
|
||||||
|
tds = tr.getElementsByTagName('td')
|
||||||
|
if tds:
|
||||||
|
i = 0
|
||||||
|
vals = {}
|
||||||
|
for td in tds:
|
||||||
|
if self.table_items[i] == 'name':
|
||||||
|
vals['link'] = get_link(td).strip()
|
||||||
|
vals[self.table_items[i]] = get_text(td).strip()
|
||||||
|
i += 1
|
||||||
|
vals['engine_url'] = self.url
|
||||||
|
if not vals['seeds'].isdigit():
|
||||||
|
vals['seeds'] = 0
|
||||||
|
if not vals['leech'].isdigit():
|
||||||
|
vals['leech'] = 0
|
||||||
|
prettyPrinter(vals)
|
BIN
src/search_engine/engines/piratebay.png
Normal file
BIN
src/search_engine/engines/piratebay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 609 B |
79
src/search_engine/engines/piratebay.py
Normal file
79
src/search_engine/engines/piratebay.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#VERSION: 1.00
|
||||||
|
#AUTHORS: Fabien Devaux (fab@gnux.info)
|
||||||
|
from novaprinter import prettyPrinter
|
||||||
|
import sgmllib
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
class piratebay(object):
|
||||||
|
url = 'http://thepiratebay.org'
|
||||||
|
name = 'The Pirate Bay'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.results = []
|
||||||
|
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
||||||
|
|
||||||
|
class SimpleSGMLParser(sgmllib.SGMLParser):
|
||||||
|
def __init__(self, results, url, *args):
|
||||||
|
sgmllib.SGMLParser.__init__(self)
|
||||||
|
self.td_counter = None
|
||||||
|
self.current_item = None
|
||||||
|
self.results = results
|
||||||
|
self.url = url
|
||||||
|
self.code = 0
|
||||||
|
|
||||||
|
def start_a(self, attr):
|
||||||
|
params = dict(attr)
|
||||||
|
if params['href'].startswith('/browse'):
|
||||||
|
self.current_item = {}
|
||||||
|
self.td_counter = 0
|
||||||
|
elif params['href'].startswith('/tor'):
|
||||||
|
self.code = params['href'].split('/')[2]
|
||||||
|
elif params['href'].startswith('http://torrents.thepiratebay.org/%s'%self.code):
|
||||||
|
self.current_item['link']=params['href'].strip()
|
||||||
|
self.td_counter = self.td_counter+1
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
if self.td_counter == 1:
|
||||||
|
if not self.current_item.has_key('name'):
|
||||||
|
self.current_item['name'] = ''
|
||||||
|
self.current_item['name']+= data.strip()
|
||||||
|
if self.td_counter == 5:
|
||||||
|
if not self.current_item.has_key('size'):
|
||||||
|
self.current_item['size'] = ''
|
||||||
|
self.current_item['size']+= data.strip()
|
||||||
|
elif self.td_counter == 6:
|
||||||
|
if not self.current_item.has_key('seeds'):
|
||||||
|
self.current_item['seeds'] = ''
|
||||||
|
self.current_item['seeds']+= data.strip()
|
||||||
|
elif self.td_counter == 7:
|
||||||
|
if not self.current_item.has_key('leech'):
|
||||||
|
self.current_item['leech'] = ''
|
||||||
|
self.current_item['leech']+= data.strip()
|
||||||
|
|
||||||
|
def start_td(self,attr):
|
||||||
|
if isinstance(self.td_counter,int):
|
||||||
|
self.td_counter += 1
|
||||||
|
if self.td_counter > 7:
|
||||||
|
self.td_counter = None
|
||||||
|
# Display item
|
||||||
|
if self.current_item:
|
||||||
|
self.current_item['engine_url'] = self.url
|
||||||
|
if not self.current_item['seeds'].isdigit():
|
||||||
|
self.current_item['seeds'] = 0
|
||||||
|
if not self.current_item['leech'].isdigit():
|
||||||
|
self.current_item['leech'] = 0
|
||||||
|
prettyPrinter(self.current_item)
|
||||||
|
self.results.append('a')
|
||||||
|
def search(self, what):
|
||||||
|
ret = []
|
||||||
|
i = 0
|
||||||
|
order = 'se'
|
||||||
|
while True:
|
||||||
|
results = []
|
||||||
|
parser = self.SimpleSGMLParser(results, self.url)
|
||||||
|
dat = urllib.urlopen(self.url+'/search/%s/%u/0/0' % (what, i)).read()
|
||||||
|
parser.feed(dat)
|
||||||
|
parser.close()
|
||||||
|
if len(results) <= 0:
|
||||||
|
break
|
||||||
|
i += 1
|
BIN
src/search_engine/engines/torrentreactor.png
Normal file
BIN
src/search_engine/engines/torrentreactor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 529 B |
78
src/search_engine/engines/torrentreactor.py
Normal file
78
src/search_engine/engines/torrentreactor.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#VERSION: 1.00
|
||||||
|
#AUTHORS: Gekko Dam Beer (gekko04@users.sourceforge.net)
|
||||||
|
from novaprinter import prettyPrinter
|
||||||
|
import sgmllib
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
class torrentreactor(object):
|
||||||
|
url = 'http://www.torrentreactor.net'
|
||||||
|
name = 'TorrentReactor.Net'
|
||||||
|
|
||||||
|
class SimpleSGMLParser(sgmllib.SGMLParser):
|
||||||
|
def __init__(self, results, url, *args):
|
||||||
|
sgmllib.SGMLParser.__init__(self)
|
||||||
|
self.td_counter = None
|
||||||
|
self.current_item = None
|
||||||
|
self.results = results
|
||||||
|
self.id = None
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def start_a(self, attr):
|
||||||
|
params = dict(attr)
|
||||||
|
if params['href'].startswith('http://dl.torrentreactor.net/download.php'):
|
||||||
|
self.current_item = {}
|
||||||
|
self.td_counter = 0
|
||||||
|
equal = params['href'].find("=")
|
||||||
|
amp = params['href'].find("&", equal+1)
|
||||||
|
self.id = str(int(params['href'][equal+1:amp]))
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
if self.td_counter == 0:
|
||||||
|
if not self.current_item.has_key('name'):
|
||||||
|
self.current_item['name'] = ''
|
||||||
|
self.current_item['name']+= data.strip()
|
||||||
|
if self.td_counter == 1:
|
||||||
|
if not self.current_item.has_key('size'):
|
||||||
|
self.current_item['size'] = ''
|
||||||
|
self.current_item['size']+= data.strip()
|
||||||
|
elif self.td_counter == 2:
|
||||||
|
if not self.current_item.has_key('seeds'):
|
||||||
|
self.current_item['seeds'] = ''
|
||||||
|
self.current_item['seeds']+= data.strip()
|
||||||
|
elif self.td_counter == 3:
|
||||||
|
if not self.current_item.has_key('leech'):
|
||||||
|
self.current_item['leech'] = ''
|
||||||
|
self.current_item['leech']+= data.strip()
|
||||||
|
|
||||||
|
def start_td(self,attr):
|
||||||
|
if isinstance(self.td_counter,int):
|
||||||
|
self.td_counter += 1
|
||||||
|
if self.td_counter > 7:
|
||||||
|
self.td_counter = None
|
||||||
|
# add item to results
|
||||||
|
if self.current_item:
|
||||||
|
self.current_item['link']='http://download.torrentreactor.net/download.php?id=%s&name=%s'%(self.id, urllib.quote(self.current_item['name']))
|
||||||
|
self.current_item['engine_url'] = self.url
|
||||||
|
if not self.current_item['seeds'].isdigit():
|
||||||
|
self.current_item['seeds'] = 0
|
||||||
|
if not self.current_item['leech'].isdigit():
|
||||||
|
self.current_item['leech'] = 0
|
||||||
|
prettyPrinter(self.current_item)
|
||||||
|
self.has_results = True
|
||||||
|
self.results.append('a')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.results = []
|
||||||
|
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
||||||
|
|
||||||
|
def search(self, what):
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
results = []
|
||||||
|
parser = self.SimpleSGMLParser(results, self.url)
|
||||||
|
dat = urllib.urlopen(self.url+'/search.php?search=&words=%s&cid=&sid=&type=2&orderby=a.seeds&asc=0&skip=%s'%(what,(i*35))).read().decode('utf-8', 'replace')
|
||||||
|
parser.feed(dat)
|
||||||
|
parser.close()
|
||||||
|
if len(results) <= 0:
|
||||||
|
break
|
||||||
|
i += 1
|
5
src/search_engine/engines/versions.txt
Normal file
5
src/search_engine/engines/versions.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
isohunt: 1.00
|
||||||
|
torrentreactor: 1.00
|
||||||
|
btjunkie: 1.00
|
||||||
|
mininova: 1.00
|
||||||
|
piratebay: 1.00
|
|
@ -1,503 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Version: 2.04
|
|
||||||
# Changelog:
|
|
||||||
# - Fixed TorrentReactor search engine
|
|
||||||
|
|
||||||
# Version: 2.03
|
|
||||||
# Changelog:
|
|
||||||
# - Little fix for mininova search engine when file name contain '<='
|
|
||||||
|
|
||||||
# Version: 2.02
|
|
||||||
# Changelog:
|
|
||||||
# - Fixed mininova search engine
|
|
||||||
|
|
||||||
# Version: 2.01
|
|
||||||
# Changelog:
|
|
||||||
# - Use multiple threads to optimize speed
|
|
||||||
|
|
||||||
# Version: 2.00
|
|
||||||
# Changelog:
|
|
||||||
# - Fixed ThePirateBay search engine
|
|
||||||
# - Fixed Meganova search engine
|
|
||||||
# - Fixed Mininova search engine
|
|
||||||
|
|
||||||
# Version: 1.90
|
|
||||||
# Changelog:
|
|
||||||
# - Various fixes
|
|
||||||
|
|
||||||
# Version: 1.80
|
|
||||||
# Changelog:
|
|
||||||
# - Fixed links from isohunt
|
|
||||||
|
|
||||||
# Version: 1.70
|
|
||||||
# Changelog:
|
|
||||||
# - merged with qbittorrent branch (code cleanup, indentation mistakes)
|
|
||||||
# - separate standalone and slave mode
|
|
||||||
# - added btjunkie
|
|
||||||
# - added meganova
|
|
||||||
# - added multithreaded mode
|
|
||||||
|
|
||||||
# End Changelog
|
|
||||||
|
|
||||||
# Author:
|
|
||||||
# Fabien Devaux <fab AT gnux DOT info>
|
|
||||||
# Contributors:
|
|
||||||
# Christophe Dumez <chris@qbittorrent.org> (qbittorrent integration)
|
|
||||||
# Thanks to gab #gcu @ irc.freenode.net (multipage support on PirateBay)
|
|
||||||
# Thanks to Elias <gekko04@users.sourceforge.net> (torrentreactor and isohunt search engines)
|
|
||||||
#
|
|
||||||
# Licence: BSD
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import urllib
|
|
||||||
import sgmllib
|
|
||||||
from xml.dom import minidom
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import cgi
|
|
||||||
import traceback
|
|
||||||
import threading
|
|
||||||
|
|
||||||
STANDALONE = False
|
|
||||||
THREADED = True
|
|
||||||
|
|
||||||
if os.environ.has_key('QBITTORRENT'):
|
|
||||||
STANDALONE = False
|
|
||||||
|
|
||||||
best_ratios = []
|
|
||||||
|
|
||||||
def prettyPrinter(dictionnary):
|
|
||||||
print "%(link)s|%(name)s|%(size)s|%(seeds)s|%(leech)s|%(engine_url)s"%dictionnary
|
|
||||||
|
|
||||||
if STANDALONE:
|
|
||||||
def termPrettyPrinter(dictionnary):
|
|
||||||
if isinstance( dictionnary['size'], int):
|
|
||||||
dictionnary['size'] = bytesToHuman(dictionnary['size'])
|
|
||||||
try:
|
|
||||||
print "%(seeds)5s/%(leech)5s | %(size)10s | %(name)s"%dictionnary
|
|
||||||
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
||||||
print "%(seeds)5s/%(leech)5s | %(size)10s | <unprintable title>"%dictionnary
|
|
||||||
try:
|
|
||||||
print "wget '%s'"%dictionnary['link'].replace("'","\\'")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
dictionnary['seeds'] = int( dictionnary['seeds'] ) or 0.00000001
|
|
||||||
dictionnary['leech'] = int( dictionnary['leech'] ) or 0.00000001
|
|
||||||
best_ratios.append(dictionnary)
|
|
||||||
|
|
||||||
globals()['prettyPrinter'] = termPrettyPrinter
|
|
||||||
|
|
||||||
def bytesToHuman(filesize):
|
|
||||||
"""
|
|
||||||
Convert float (size in bytes) to readable string
|
|
||||||
"""
|
|
||||||
decimators = ('k','M','G','T')
|
|
||||||
unit = ''
|
|
||||||
for n in range(len(decimators)):
|
|
||||||
if filesize > 1100.0:
|
|
||||||
filesize /= 1024.0
|
|
||||||
unit = decimators[n]
|
|
||||||
return '%.1f%sB'%(filesize, unit)
|
|
||||||
|
|
||||||
def anySizeToBytes(size_string):
|
|
||||||
"""
|
|
||||||
Convert a string like '1 KB' to '1024' (bytes)
|
|
||||||
"""
|
|
||||||
# separate integer from unit
|
|
||||||
try:
|
|
||||||
size, unit = size_string.split()
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
try:
|
|
||||||
size = size_string.strip()
|
|
||||||
unit = ''.join([c for c in size if c.isalpha()])
|
|
||||||
size = size[:-len(unit)]
|
|
||||||
except(ValueError, TypeError):
|
|
||||||
return -1
|
|
||||||
|
|
||||||
size = float(size)
|
|
||||||
short_unit = unit.upper()[0]
|
|
||||||
|
|
||||||
# convert
|
|
||||||
units_dict = { 'T': 40, 'G': 30, 'M': 20, 'K': 10 }
|
|
||||||
if units_dict.has_key( short_unit ):
|
|
||||||
size = size * 2**units_dict[short_unit]
|
|
||||||
return int(size)
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Every engine should have a "search" method taking
|
|
||||||
# a space-free string as parameter (ex. "family+guy")
|
|
||||||
# it should call prettyPrinter() with a dict as parameter
|
|
||||||
# see above for dict keys
|
|
||||||
# As a convention, try to list results by decrasing number of seeds or similar
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
class PirateBay(object):
|
|
||||||
url = 'http://thepiratebay.org'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.results = []
|
|
||||||
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
|
||||||
|
|
||||||
class SimpleSGMLParser(sgmllib.SGMLParser):
|
|
||||||
def __init__(self, results, url, *args):
|
|
||||||
sgmllib.SGMLParser.__init__(self)
|
|
||||||
self.td_counter = None
|
|
||||||
self.current_item = None
|
|
||||||
self.results = results
|
|
||||||
self.url = url
|
|
||||||
self.code = 0
|
|
||||||
|
|
||||||
def start_a(self, attr):
|
|
||||||
params = dict(attr)
|
|
||||||
if params['href'].startswith('/browse'):
|
|
||||||
self.current_item = {}
|
|
||||||
self.td_counter = 0
|
|
||||||
elif params['href'].startswith('/tor'):
|
|
||||||
self.code = params['href'].split('/')[2]
|
|
||||||
elif params['href'].startswith('http://torrents.thepiratebay.org/%s'%self.code):
|
|
||||||
self.current_item['link']=params['href'].strip()
|
|
||||||
self.td_counter = self.td_counter+1
|
|
||||||
|
|
||||||
def handle_data(self, data):
|
|
||||||
if self.td_counter == 1:
|
|
||||||
if not self.current_item.has_key('name'):
|
|
||||||
self.current_item['name'] = ''
|
|
||||||
self.current_item['name']+= data.strip()
|
|
||||||
if self.td_counter == 5:
|
|
||||||
if not self.current_item.has_key('size'):
|
|
||||||
self.current_item['size'] = ''
|
|
||||||
self.current_item['size']+= data.strip()
|
|
||||||
elif self.td_counter == 6:
|
|
||||||
if not self.current_item.has_key('seeds'):
|
|
||||||
self.current_item['seeds'] = ''
|
|
||||||
self.current_item['seeds']+= data.strip()
|
|
||||||
elif self.td_counter == 7:
|
|
||||||
if not self.current_item.has_key('leech'):
|
|
||||||
self.current_item['leech'] = ''
|
|
||||||
self.current_item['leech']+= data.strip()
|
|
||||||
|
|
||||||
def start_td(self,attr):
|
|
||||||
if isinstance(self.td_counter,int):
|
|
||||||
self.td_counter += 1
|
|
||||||
if self.td_counter > 7:
|
|
||||||
self.td_counter = None
|
|
||||||
# Display item
|
|
||||||
if self.current_item:
|
|
||||||
self.current_item['engine_url'] = self.url
|
|
||||||
self.current_item['size']= anySizeToBytes(self.current_item['size'])
|
|
||||||
if not self.current_item['seeds'].isdigit():
|
|
||||||
self.current_item['seeds'] = 0
|
|
||||||
if not self.current_item['leech'].isdigit():
|
|
||||||
self.current_item['leech'] = 0
|
|
||||||
prettyPrinter(self.current_item)
|
|
||||||
self.results.append('a')
|
|
||||||
def search(self, what):
|
|
||||||
ret = []
|
|
||||||
i = 0
|
|
||||||
order = 'se'
|
|
||||||
while True:
|
|
||||||
results = []
|
|
||||||
parser = self.SimpleSGMLParser(results, self.url)
|
|
||||||
dat = urllib.urlopen(self.url+'/search/%s/%u/0/0' % (what, i)).read()
|
|
||||||
parser.feed(dat)
|
|
||||||
parser.close()
|
|
||||||
if len(results) <= 0:
|
|
||||||
break
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
class Mininova(object):
|
|
||||||
url = 'http://www.mininova.org'
|
|
||||||
table_items = 'added cat name size seeds leech'.split()
|
|
||||||
|
|
||||||
def search(self, what):
|
|
||||||
order = 'seeds' # must be one in self.table_items
|
|
||||||
|
|
||||||
def get_link(lnk):
|
|
||||||
lnks = lnk.getElementsByTagName('a')
|
|
||||||
if lnks.item(0).attributes.get('href').value.startswith('/faq'):
|
|
||||||
if len(lnks) > 1:
|
|
||||||
return self.url+lnks.item(1).attributes.get('href').value
|
|
||||||
else:
|
|
||||||
return self.url+lnks.item(0).attributes.get('href').value
|
|
||||||
|
|
||||||
def get_text(txt):
|
|
||||||
if txt.nodeType == txt.TEXT_NODE:
|
|
||||||
return txt.toxml()
|
|
||||||
else:
|
|
||||||
return ''.join([ get_text(n) for n in txt.childNodes])
|
|
||||||
dat = urllib.urlopen(self.url+'/search/%s/seeds'%(what,)).read().decode('utf-8', 'replace')
|
|
||||||
dat = re.sub("<a href=\"http://www.boardreader.com/index.php.*\"", "<a href=\"plop\"", dat)
|
|
||||||
dat = re.sub("<=", "<=", dat)
|
|
||||||
x = minidom.parseString(dat.encode('utf-8', 'replace'))
|
|
||||||
table = x.getElementsByTagName('table').item(0)
|
|
||||||
if not table: return
|
|
||||||
for tr in table.getElementsByTagName('tr'):
|
|
||||||
tds = tr.getElementsByTagName('td')
|
|
||||||
if tds:
|
|
||||||
i = 0
|
|
||||||
vals = {}
|
|
||||||
for td in tds:
|
|
||||||
if self.table_items[i] == 'name':
|
|
||||||
vals['link'] = get_link(td).strip()
|
|
||||||
vals[self.table_items[i]] = get_text(td).strip()
|
|
||||||
i += 1
|
|
||||||
vals['engine_url'] = self.url
|
|
||||||
vals['size']= anySizeToBytes(vals['size'])
|
|
||||||
if not vals['seeds'].isdigit():
|
|
||||||
vals['seeds'] = 0
|
|
||||||
if not vals['leech'].isdigit():
|
|
||||||
vals['leech'] = 0
|
|
||||||
prettyPrinter(vals)
|
|
||||||
|
|
||||||
# TODO: add multipage
|
|
||||||
class BtJunkie(object):
|
|
||||||
url = 'http://btjunkie.org'
|
|
||||||
|
|
||||||
def search(self, what):
|
|
||||||
dat = urllib.urlopen(self.url+'/search?q=%s'%what).read().decode('utf8', 'replace')
|
|
||||||
# I know it's not very readable, but the SGML parser feels in pain
|
|
||||||
section_re = re.compile('(?s)href="/torrent\?do=download.*?<tr>')
|
|
||||||
torrent_re = re.compile('(?s)href="(?P<link>.*?do=download[^"]+).*?'
|
|
||||||
'class="BlckUnd">(?P<name>.*?)</a>.*?'
|
|
||||||
'>(?P<size>\d+MB)</font>.*?'
|
|
||||||
'>(?P<seeds>\d+)</font>.*?'
|
|
||||||
'>(?P<leech>\d+)</font>')
|
|
||||||
for match in section_re.finditer(dat):
|
|
||||||
txt = match.group(0)
|
|
||||||
m = torrent_re.search(txt)
|
|
||||||
if m:
|
|
||||||
torrent_infos = m.groupdict()
|
|
||||||
torrent_infos['name'] = re.sub('</?font.*?>', '', torrent_infos['name'])
|
|
||||||
torrent_infos['engine_url'] = self.url
|
|
||||||
torrent_infos['size'] = anySizeToBytes(torrent_infos['size'])
|
|
||||||
torrent_infos['link'] = self.url+torrent_infos['link']
|
|
||||||
prettyPrinter(torrent_infos)
|
|
||||||
|
|
||||||
class MegaNova(object):
|
|
||||||
url = 'http://www.meganova.org'
|
|
||||||
|
|
||||||
def search(self, what):
|
|
||||||
dat = urllib.urlopen(self.url+'/find/%s/4/1.html'%what).read().decode('utf8', 'replace')
|
|
||||||
print 'url is ' + self.url+'/find/%s/4/1.html'%what
|
|
||||||
# I know it's not very readable, but the SGML parser feels in pain
|
|
||||||
|
|
||||||
section_re = re.compile('(?s)<td><a class="name".*?</tr')
|
|
||||||
torrent_re = re.compile('(?s)href="(?P<link>/torrent/.*?)".*?'
|
|
||||||
'<span.*?>(?P<name>.*?)</span>.*?'
|
|
||||||
'>(?P<size>[0-9.]+\s+.B).*?'
|
|
||||||
'>(?P<seeds>\d+)<.*?'
|
|
||||||
'>(?P<leech>\d+)<')
|
|
||||||
|
|
||||||
for match in section_re.finditer(dat):
|
|
||||||
txt = match.group(0)
|
|
||||||
m = torrent_re.search(txt)
|
|
||||||
if m:
|
|
||||||
torrent_infos = m.groupdict()
|
|
||||||
torrent_infos['engine_url'] = self.url
|
|
||||||
torrent_infos['size'] = anySizeToBytes(torrent_infos['size'])
|
|
||||||
torrent_infos['link'] = self.url+torrent_infos['link']
|
|
||||||
prettyPrinter(torrent_infos)
|
|
||||||
|
|
||||||
class Reactor(object):
|
|
||||||
url = 'http://www.torrentreactor.net'
|
|
||||||
|
|
||||||
class SimpleSGMLParser(sgmllib.SGMLParser):
|
|
||||||
def __init__(self, results, url, *args):
|
|
||||||
sgmllib.SGMLParser.__init__(self)
|
|
||||||
self.td_counter = None
|
|
||||||
self.current_item = None
|
|
||||||
self.results = results
|
|
||||||
self.id = None
|
|
||||||
self.url = url
|
|
||||||
|
|
||||||
def start_a(self, attr):
|
|
||||||
params = dict(attr)
|
|
||||||
if params['href'].startswith('http://dl.torrentreactor.net/download.php'):
|
|
||||||
self.current_item = {}
|
|
||||||
self.td_counter = 0
|
|
||||||
equal = params['href'].find("=")
|
|
||||||
amp = params['href'].find("&", equal+1)
|
|
||||||
self.id = str(int(params['href'][equal+1:amp]))
|
|
||||||
|
|
||||||
def handle_data(self, data):
|
|
||||||
if self.td_counter == 0:
|
|
||||||
if not self.current_item.has_key('name'):
|
|
||||||
self.current_item['name'] = ''
|
|
||||||
self.current_item['name']+= data.strip()
|
|
||||||
if self.td_counter == 1:
|
|
||||||
if not self.current_item.has_key('size'):
|
|
||||||
self.current_item['size'] = ''
|
|
||||||
self.current_item['size']+= data.strip()
|
|
||||||
elif self.td_counter == 2:
|
|
||||||
if not self.current_item.has_key('seeds'):
|
|
||||||
self.current_item['seeds'] = ''
|
|
||||||
self.current_item['seeds']+= data.strip()
|
|
||||||
elif self.td_counter == 3:
|
|
||||||
if not self.current_item.has_key('leech'):
|
|
||||||
self.current_item['leech'] = ''
|
|
||||||
self.current_item['leech']+= data.strip()
|
|
||||||
|
|
||||||
def start_td(self,attr):
|
|
||||||
if isinstance(self.td_counter,int):
|
|
||||||
self.td_counter += 1
|
|
||||||
if self.td_counter > 7:
|
|
||||||
self.td_counter = None
|
|
||||||
# add item to results
|
|
||||||
if self.current_item:
|
|
||||||
self.current_item['link']='http://download.torrentreactor.net/download.php?id=%s&name=%s'%(self.id, urllib.quote(self.current_item['name']))
|
|
||||||
self.current_item['engine_url'] = self.url
|
|
||||||
self.current_item['size']= anySizeToBytes(self.current_item['size'])
|
|
||||||
if not self.current_item['seeds'].isdigit():
|
|
||||||
self.current_item['seeds'] = 0
|
|
||||||
if not self.current_item['leech'].isdigit():
|
|
||||||
self.current_item['leech'] = 0
|
|
||||||
prettyPrinter(self.current_item)
|
|
||||||
self.has_results = True
|
|
||||||
self.results.append('a')
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.results = []
|
|
||||||
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
|
||||||
|
|
||||||
def search(self, what):
|
|
||||||
i = 0
|
|
||||||
while True:
|
|
||||||
results = []
|
|
||||||
parser = self.SimpleSGMLParser(results, self.url)
|
|
||||||
dat = urllib.urlopen(self.url+'/search.php?search=&words=%s&cid=&sid=&type=2&orderby=a.seeds&asc=0&skip=%s'%(what,(i*35))).read().decode('utf-8', 'replace')
|
|
||||||
parser.feed(dat)
|
|
||||||
parser.close()
|
|
||||||
if len(results) <= 0:
|
|
||||||
break
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
class Isohunt(object):
|
|
||||||
url = 'http://isohunt.com'
|
|
||||||
|
|
||||||
class SimpleSGMLParser(sgmllib.SGMLParser):
|
|
||||||
def __init__(self, results, url, *args):
|
|
||||||
sgmllib.SGMLParser.__init__(self)
|
|
||||||
self.td_counter = None
|
|
||||||
self.current_item = None
|
|
||||||
self.results = results
|
|
||||||
self.url = url
|
|
||||||
|
|
||||||
def start_tr(self, attr):
|
|
||||||
params = dict(attr)
|
|
||||||
if 'onclick' in params:
|
|
||||||
Durl='http://isohunt.com/download'
|
|
||||||
self.current_item = {}
|
|
||||||
self.td_counter = 0
|
|
||||||
try:
|
|
||||||
self.current_item['link'] = '%s/%s'%(Durl, params['onclick'].split('/')[2])
|
|
||||||
except IndexError:
|
|
||||||
self.current_item['link'] = None
|
|
||||||
|
|
||||||
def handle_data(self, data):
|
|
||||||
if self.td_counter == 3:
|
|
||||||
if not self.current_item.has_key('name'):
|
|
||||||
self.current_item['name'] = ''
|
|
||||||
self.current_item['name']+= data.strip()
|
|
||||||
if self.td_counter == 4:
|
|
||||||
if not self.current_item.has_key('size'):
|
|
||||||
self.current_item['size'] = ''
|
|
||||||
self.current_item['size']+= data.strip()
|
|
||||||
if self.td_counter == 5:
|
|
||||||
if not self.current_item.has_key('seeds'):
|
|
||||||
self.current_item['seeds'] = ''
|
|
||||||
self.current_item['seeds']+= data.strip()
|
|
||||||
if self.td_counter == 6:
|
|
||||||
if not self.current_item.has_key('leech'):
|
|
||||||
self.current_item['leech'] = ''
|
|
||||||
self.current_item['leech']+= data.strip()
|
|
||||||
|
|
||||||
def start_td(self,attr):
|
|
||||||
if isinstance(self.td_counter,int):
|
|
||||||
self.td_counter += 1
|
|
||||||
if self.td_counter > 7:
|
|
||||||
self.td_counter = None
|
|
||||||
# add item to results
|
|
||||||
if self.current_item:
|
|
||||||
self.current_item['engine_url'] = self.url
|
|
||||||
self.current_item['size']= anySizeToBytes(self.current_item['size'])
|
|
||||||
if not self.current_item.has_key('seeds') or not self.current_item['seeds'].isdigit():
|
|
||||||
self.current_item['seeds'] = 0
|
|
||||||
if not self.current_item.has_key('leech') or not self.current_item['leech'].isdigit():
|
|
||||||
self.current_item['leech'] = 0
|
|
||||||
if self.current_item['link'] is not None:
|
|
||||||
prettyPrinter(self.current_item)
|
|
||||||
self.results.append('a')
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.results = []
|
|
||||||
self.parser = self.SimpleSGMLParser(self.results, self.url)
|
|
||||||
|
|
||||||
def search(self, what):
|
|
||||||
i = 1
|
|
||||||
while True:
|
|
||||||
results = []
|
|
||||||
parser = self.SimpleSGMLParser(results, self.url)
|
|
||||||
dat = urllib.urlopen(self.url+'/torrents.php?ihq=%s&ihp=%s'%(what,i)).read().decode('utf-8', 'replace')
|
|
||||||
parser.feed(dat)
|
|
||||||
parser.close()
|
|
||||||
if len(results) <= 0:
|
|
||||||
break
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
class EngineLauncher(threading.Thread):
|
|
||||||
def __init__(self, engine, what):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.engine = engine
|
|
||||||
self.what = what
|
|
||||||
def run(self):
|
|
||||||
self.engine.search(self.what)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
available_engines_list = BtJunkie, MegaNova, Mininova, PirateBay, Reactor, Isohunt
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
raise SystemExit('./nova.py [all|engine1[,engine2]*] <keywords>\navailable engines: %s'%
|
|
||||||
(','.join(e.__name__ for e in available_engines_list)))
|
|
||||||
|
|
||||||
engines_list = [e.lower() for e in sys.argv[1].strip().split(',')]
|
|
||||||
|
|
||||||
if 'all' in engines_list:
|
|
||||||
engines_list = [e.__name__.lower() for e in available_engines_list]
|
|
||||||
|
|
||||||
selected_engines = set(e for e in available_engines_list if e.__name__.lower() in engines_list)
|
|
||||||
|
|
||||||
if not selected_engines:
|
|
||||||
selected_engines = [BtJunkie]
|
|
||||||
what = '+'.join(sys.argv[1:])
|
|
||||||
else:
|
|
||||||
what = '+'.join(sys.argv[2:])
|
|
||||||
|
|
||||||
threads = []
|
|
||||||
for engine in selected_engines:
|
|
||||||
try:
|
|
||||||
if THREADED:
|
|
||||||
l = EngineLauncher( engine(), what )
|
|
||||||
threads.append(l)
|
|
||||||
l.start()
|
|
||||||
else:
|
|
||||||
engine().search(what)
|
|
||||||
except:
|
|
||||||
if STANDALONE:
|
|
||||||
traceback.print_exc()
|
|
||||||
if THREADED:
|
|
||||||
for t in threads:
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
best_ratios.sort(lambda a,b : cmp(a['seeds']-a['leech'], b['seeds']-b['leech']))
|
|
||||||
|
|
||||||
max_results = 10
|
|
||||||
|
|
||||||
print "########## TOP %d RATIOS ##########"%max_results
|
|
||||||
|
|
||||||
for br in best_ratios:
|
|
||||||
if br['seeds'] > 1: # avoid those with 0 leech to be max rated
|
|
||||||
prettyPrinter(br)
|
|
||||||
max_results -= 1
|
|
||||||
if not max_results:
|
|
||||||
break
|
|
90
src/search_engine/nova2.py
Executable file
90
src/search_engine/nova2.py
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#VERSION: 1.00
|
||||||
|
|
||||||
|
# Author:
|
||||||
|
# Fabien Devaux <fab AT gnux DOT info>
|
||||||
|
# Contributors:
|
||||||
|
# Christophe Dumez <chris@qbittorrent.org> (qbittorrent integration)
|
||||||
|
# Thanks to gab #gcu @ irc.freenode.net (multipage support on PirateBay)
|
||||||
|
# Thanks to Elias <gekko04@users.sourceforge.net> (torrentreactor and isohunt search engines)
|
||||||
|
#
|
||||||
|
# Licence: BSD
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
THREADED = True
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Every engine should have a "search" method taking
|
||||||
|
# a space-free string as parameter (ex. "family+guy")
|
||||||
|
# it should call prettyPrinter() with a dict as parameter.
|
||||||
|
# The keys in the dict must be: link,name,size,seeds,leech,engine_url
|
||||||
|
# As a convention, try to list results by decrasing number of seeds or similar
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
supported_engines = []
|
||||||
|
|
||||||
|
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)
|
||||||
|
supported_engines.append(e)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class EngineLauncher(threading.Thread):
|
||||||
|
def __init__(self, engine, what):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.engine = engine
|
||||||
|
self.what = what
|
||||||
|
def run(self):
|
||||||
|
self.engine.search(self.what)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
raise SystemExit('./nova.py [all|engine1[,engine2]*] <keywords>\navailable engines: %s'%
|
||||||
|
(','.join(supported_engines)))
|
||||||
|
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if sys.argv[1] == "--supported_engines":
|
||||||
|
print ','.join(supported_engines)
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.argv[1] == "--supported_engines_infos":
|
||||||
|
res = []
|
||||||
|
for e in supported_engines:
|
||||||
|
exec "res.append(%s().name+'|'+%s().url)"%(e,e)
|
||||||
|
print ','.join(res)
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
raise SystemExit('./nova.py [all|engine1[,engine2]*] <keywords>\navailable engines: %s'%
|
||||||
|
(','.join(supported_engines)))
|
||||||
|
|
||||||
|
engines_list = [e.lower() for e in sys.argv[1].strip().split(',')]
|
||||||
|
|
||||||
|
if 'all' in engines_list:
|
||||||
|
engines_list = supported_engines
|
||||||
|
|
||||||
|
what = '+'.join(sys.argv[2:])
|
||||||
|
|
||||||
|
threads = []
|
||||||
|
for engine in engines_list:
|
||||||
|
try:
|
||||||
|
if THREADED:
|
||||||
|
exec "l = EngineLauncher(%s(), what)" % engine
|
||||||
|
threads.append(l)
|
||||||
|
l.start()
|
||||||
|
else:
|
||||||
|
engine().search(what)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if THREADED:
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
27
src/search_engine/novaprinter.py
Normal file
27
src/search_engine/novaprinter.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
def prettyPrinter(dictionnary):
|
||||||
|
dictionnary['size'] = anySizeToBytes(dictionnary['size'])
|
||||||
|
print "%(link)s|%(name)s|%(size)s|%(seeds)s|%(leech)s|%(engine_url)s" % dictionnary
|
||||||
|
|
||||||
|
def anySizeToBytes(size_string):
|
||||||
|
"""
|
||||||
|
Convert a string like '1 KB' to '1024' (bytes)
|
||||||
|
"""
|
||||||
|
# separate integer from unit
|
||||||
|
try:
|
||||||
|
size, unit = size_string.split()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
try:
|
||||||
|
size = size_string.strip()
|
||||||
|
unit = ''.join([c for c in size if c.isalpha()])
|
||||||
|
size = size[:-len(unit)]
|
||||||
|
except(ValueError, TypeError):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
size = float(size)
|
||||||
|
short_unit = unit.upper()[0]
|
||||||
|
|
||||||
|
# convert
|
||||||
|
units_dict = { 'T': 40, 'G': 30, 'M': 20, 'K': 10 }
|
||||||
|
if units_dict.has_key( short_unit ):
|
||||||
|
size = size * 2**units_dict[short_unit]
|
||||||
|
return int(size)
|
|
@ -150,12 +150,13 @@ HEADERS += GUI.h misc.h options_imp.h about_imp.h \
|
||||||
bittorrent.h searchEngine.h \
|
bittorrent.h searchEngine.h \
|
||||||
rss.h rss_imp.h FinishedTorrents.h \
|
rss.h rss_imp.h FinishedTorrents.h \
|
||||||
allocationDlg.h FinishedListDelegate.h \
|
allocationDlg.h FinishedListDelegate.h \
|
||||||
qtorrenthandle.h downloadingTorrents.h
|
qtorrenthandle.h downloadingTorrents.h \
|
||||||
|
engineSelectDlg.h
|
||||||
FORMS += MainWindow.ui options.ui about.ui \
|
FORMS += MainWindow.ui options.ui about.ui \
|
||||||
properties.ui createtorrent.ui preview.ui \
|
properties.ui createtorrent.ui preview.ui \
|
||||||
login.ui downloadFromURL.ui addTorrentDialog.ui \
|
login.ui downloadFromURL.ui addTorrentDialog.ui \
|
||||||
search.ui rss.ui seeding.ui bandwidth_limit.ui \
|
search.ui rss.ui seeding.ui bandwidth_limit.ui \
|
||||||
download.ui
|
download.ui engineSelect.ui
|
||||||
SOURCES += GUI.cpp \
|
SOURCES += GUI.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
options_imp.cpp \
|
options_imp.cpp \
|
||||||
|
@ -166,5 +167,6 @@ SOURCES += GUI.cpp \
|
||||||
rss_imp.cpp \
|
rss_imp.cpp \
|
||||||
FinishedTorrents.cpp \
|
FinishedTorrents.cpp \
|
||||||
qtorrenthandle.cpp \
|
qtorrenthandle.cpp \
|
||||||
downloadingTorrents.cpp
|
downloadingTorrents.cpp \
|
||||||
|
engineSelectDlg.cpp
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,27 @@ lang_file = open('lang.qrc', 'w')
|
||||||
lang_file.write(output)
|
lang_file.write(output)
|
||||||
lang_file.close()
|
lang_file.close()
|
||||||
|
|
||||||
|
# update search_engine directory
|
||||||
|
search_list = []
|
||||||
|
for root, dirs, files in os.walk('search_engine'):
|
||||||
|
for file in files:
|
||||||
|
if file.startswith("__"):
|
||||||
|
continue
|
||||||
|
if splitext(file)[-1] in ('.py', '.png'):
|
||||||
|
search_list.append(join(root, file))
|
||||||
|
|
||||||
|
output = '''<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource>
|
||||||
|
'''
|
||||||
|
for file in search_list:
|
||||||
|
output += ' <file>%s</file>'%(file)
|
||||||
|
output += os.linesep
|
||||||
|
output += '''</qresource>
|
||||||
|
</RCC>'''
|
||||||
|
search_file = open('search.qrc', 'w')
|
||||||
|
search_file.write(output)
|
||||||
|
search_file.close()
|
||||||
|
|
||||||
# update icons files directory
|
# update icons files directory
|
||||||
icons_list = []
|
icons_list = []
|
||||||
for root, dirs, files in os.walk('Icons'):
|
for root, dirs, files in os.walk('Icons'):
|
||||||
|
|
Loading…
Reference in a new issue