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