Christophe Dumez
17 years ago
28 changed files with 1428 additions and 935 deletions
@ -0,0 +1,117 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,5 +1,16 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0"> |
||||
<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> |
||||
</RCC> |
After Width: | Height: | Size: 622 B |
@ -0,0 +1,29 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
isohunt: 1.00 |
||||
torrentreactor: 1.00 |
||||
btjunkie: 1.00 |
||||
mininova: 1.00 |
||||
piratebay: 1.00 |
@ -1,503 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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