Christophe Dumez
17 years ago
28 changed files with 1428 additions and 935 deletions
@ -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> |
@ -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())); |
||||||
|
} |
||||||
|
} |
@ -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 |
@ -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> |
After Width: | Height: | Size: 622 B |
@ -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) |
After Width: | Height: | Size: 633 B |
@ -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 |
After Width: | Height: | Size: 365 B |
@ -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) |
After Width: | Height: | Size: 609 B |
@ -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 |
After Width: | Height: | Size: 529 B |
@ -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 |
@ -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 |
|
@ -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() |
@ -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) |
Loading…
Reference in new issue