diff --git a/Changelog b/Changelog index a184ff23e..2c2c717ca 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,6 @@ * Unknown - Christophe Dumez - v1.5.0 - FEATURE: Added Magnet URI support + - FEATURE: Search engine supports category-based requests - FEATURE: Make use of torrent enclosure in RSS feeds for direct download - FEATURE: Implemented a RSS feed downloader with filter support - FEATURE: Save old RSS item to hard disk to remember them on start up diff --git a/src/FinishedTorrents.cpp b/src/FinishedTorrents.cpp index e287dcd27..715c0a5a4 100644 --- a/src/FinishedTorrents.cpp +++ b/src/FinishedTorrents.cpp @@ -462,7 +462,7 @@ void FinishedTorrents::displayFinishedListMenu(const QPoint&){ */ // hide/show columns menu -void FinishedTorrents::displayFinishedHoSMenu(const QPoint& pos){ +void FinishedTorrents::displayFinishedHoSMenu(const QPoint&){ QMenu hideshowColumn(this); hideshowColumn.setTitle(tr("Hide or Show Column")); int lastCol = F_RATIO; @@ -470,7 +470,7 @@ void FinishedTorrents::displayFinishedHoSMenu(const QPoint& pos){ hideshowColumn.addAction(getActionHoSCol(i)); } // Call menu - hideshowColumn.exec(mapToGlobal(pos)+QPoint(10,34)); + hideshowColumn.exec(QCursor::pos()); } // toggle hide/show a column diff --git a/src/Icons/money.png b/src/Icons/money.png deleted file mode 100644 index e9b6b4426..000000000 Binary files a/src/Icons/money.png and /dev/null differ diff --git a/src/Icons/oxygen/wallet.png b/src/Icons/oxygen/wallet.png new file mode 100644 index 000000000..0787984b6 Binary files /dev/null and b/src/Icons/oxygen/wallet.png differ diff --git a/src/download.ui b/src/download.ui index d65662d50..306afb48f 100644 --- a/src/download.ui +++ b/src/download.ui @@ -142,7 +142,7 @@ - :/Icons/money.png:/Icons/money.png + :/Icons/oxygen/wallet.png:/Icons/oxygen/wallet.png Buy it diff --git a/src/downloadingTorrents.cpp b/src/downloadingTorrents.cpp index eef682027..66d565f8c 100644 --- a/src/downloadingTorrents.cpp +++ b/src/downloadingTorrents.cpp @@ -312,7 +312,7 @@ void DownloadingTorrents::displayDLListMenu(const QPoint&) { */ // hide/show columns menu -void DownloadingTorrents::displayDLHoSMenu(const QPoint& pos){ +void DownloadingTorrents::displayDLHoSMenu(const QPoint&){ QMenu hideshowColumn(this); hideshowColumn.setTitle(tr("Hide or Show Column")); int lastCol; @@ -325,7 +325,7 @@ void DownloadingTorrents::displayDLHoSMenu(const QPoint& pos){ hideshowColumn.addAction(getActionHoSCol(i)); } // Call menu - hideshowColumn.exec(mapToGlobal(pos)+QPoint(10,10)); + hideshowColumn.exec(QCursor::pos()); } // toggle hide/show a column diff --git a/src/engineSelectDlg.cpp b/src/engineSelectDlg.cpp index 023934273..96590d1cf 100644 --- a/src/engineSelectDlg.cpp +++ b/src/engineSelectDlg.cpp @@ -43,7 +43,7 @@ #include #ifdef HAVE_ZZIP - #include +#include #endif #define ENGINE_NAME 0 @@ -51,7 +51,7 @@ #define ENGINE_STATE 2 #define ENGINE_ID 3 -engineSelectDlg::engineSelectDlg(QWidget *parent) : QDialog(parent) { +engineSelectDlg::engineSelectDlg(QWidget *parent, SupportedEngines *supported_engines) : QDialog(parent), supported_engines(supported_engines) { setupUi(this); setAttribute(Qt::WA_DeleteOnClose); pluginsTree->header()->resizeSection(0, 170); @@ -66,14 +66,14 @@ engineSelectDlg::engineSelectDlg(QWidget *parent) : QDialog(parent) { 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))); - loadSupportedSearchEngines(true); + loadSupportedSearchEngines(); + connect(supported_engines, SIGNAL(newSupportedEngine(QString)), this, SLOT(addNewEngine(QString))); 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; @@ -115,43 +115,24 @@ void engineSelectDlg::dragEnterEvent(QDragEnterEvent *event) { } } -void engineSelectDlg::saveSettings() { - qDebug("Saving engines settings"); - QStringList known_engines; - QVariantList known_enginesEnabled; - QString engine; - foreach(engine, installed_engines.keys()) { - known_engines << engine; - known_enginesEnabled << QVariant(installed_engines.value(engine, true)); - qDebug("Engine %s has state: %d", engine.toLocal8Bit().data(), installed_engines.value(engine, true)); - } - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); - settings.setValue(QString::fromUtf8("SearchEngines/knownEngines"), known_engines); - settings.setValue(QString::fromUtf8("SearchEngines/knownEnginesEnabled"), known_enginesEnabled); -} - void engineSelectDlg::on_updateButton_clicked() { // Download version file from primary server downloader->downloadUrl("http://www.dchris.eu/search_engine2/versions.txt"); } void engineSelectDlg::toggleEngineState(QTreeWidgetItem *item, int) { - int index = pluginsTree->indexOfTopLevelItem(item); - QString id = item->text(ENGINE_ID); - bool new_val = !installed_engines.value(id, true); - installed_engines[id] = new_val; - QString enabledTxt; - if(new_val){ - enabledTxt = tr("True"); - setRowColor(index, "green"); - }else{ - enabledTxt = tr("False"); - setRowColor(index, "red"); + SupportedEngine *engine = supported_engines->value(item->text(ENGINE_ID)); + engine->setEnabled(!engine->isEnabled()); + if(engine->isEnabled()) { + item->setText(ENGINE_STATE, tr("Yes")); + setRowColor(pluginsTree->indexOfTopLevelItem(item), "green"); + } else { + item->setText(ENGINE_STATE, tr("No")); + setRowColor(pluginsTree->indexOfTopLevelItem(item), "red"); } - item->setText(ENGINE_STATE, enabledTxt); } -void engineSelectDlg::displayContextMenu(const QPoint& pos) { +void engineSelectDlg::displayContextMenu(const QPoint&) { QMenu myContextMenu(this); QModelIndex index; // Enable/disable pause/start action given the DL state @@ -160,11 +141,11 @@ void engineSelectDlg::displayContextMenu(const QPoint& pos) { QTreeWidgetItem *item; foreach(item, items) { QString id = item->text(ENGINE_ID); - if(installed_engines.value(id, true) and !has_disable) { + if(supported_engines->value(id)->isEnabled() and !has_disable) { myContextMenu.addAction(actionDisable); has_disable = true; } - if(!installed_engines.value(id, true) and !has_enable) { + if(!supported_engines->value(id)->isEnabled() and !has_enable) { myContextMenu.addAction(actionEnable); has_enable = true; } @@ -172,7 +153,7 @@ void engineSelectDlg::displayContextMenu(const QPoint& pos) { } myContextMenu.addSeparator(); myContextMenu.addAction(actionUninstall); - myContextMenu.exec(mapToGlobal(pos)+QPoint(12, 58)); + myContextMenu.exec(QCursor::pos()); } void engineSelectDlg::on_closeButton_clicked() { @@ -191,8 +172,8 @@ void engineSelectDlg::on_actionUninstall_triggered() { if(QFile::exists(":/search_engine/engines/"+id+".py")) { error = true; // Disable it instead - installed_engines.insert(id, false); - item->setText(ENGINE_STATE, tr("False")); + supported_engines->value(id)->setEnabled(false); + item->setText(ENGINE_STATE, tr("No")); setRowColor(index, "red"); continue; }else { @@ -206,8 +187,8 @@ void engineSelectDlg::on_actionUninstall_triggered() { foreach(file, files) { enginesFolder.remove(file); } - // Remove it from lists - installed_engines.remove(id); + // Remove it from supported engines + delete supported_engines->take(id); delete item; change = true; } @@ -225,8 +206,8 @@ void engineSelectDlg::enableSelection() { int index = pluginsTree->indexOfTopLevelItem(item); Q_ASSERT(index != -1); QString id = item->text(ENGINE_ID); - installed_engines.insert(id, true); - item->setText(ENGINE_STATE, tr("True")); + supported_engines->value(id)->setEnabled(true); + item->setText(ENGINE_STATE, tr("Yes")); setRowColor(index, "green"); } } @@ -238,8 +219,8 @@ void engineSelectDlg::disableSelection() { int index = pluginsTree->indexOfTopLevelItem(item); Q_ASSERT(index != -1); QString id = item->text(ENGINE_ID); - installed_engines.insert(id, false); - item->setText(ENGINE_STATE, tr("False")); + supported_engines->value(id)->setEnabled(false); + item->setText(ENGINE_STATE, tr("No")); setRowColor(index, "red"); } } @@ -247,108 +228,11 @@ void engineSelectDlg::disableSelection() { // 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; icolumnCount()-1; ++i){ + for(int i=0; icolumnCount(); ++i){ item->setData(i, Qt::ForegroundRole, QVariant(QColor(color))); } } -bool engineSelectDlg::checkInstalled(QString plugin_name) const { - QProcess nova; - QStringList params; - params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py"; - params << "--supported_engines"; - nova.start("python", params, QIODevice::ReadOnly); - nova.waitForStarted(); - nova.waitForFinished(); - QByteArray result = nova.readAll(); - result = result.replace("\r", ""); - result = result.replace("\n", ""); - QList plugins_list = result.split(','); - return plugins_list.contains(plugin_name.toLocal8Bit()); -} - -void engineSelectDlg::loadSupportedSearchEngines(bool first) { - // Some clean up first - pluginsTree->clear(); - QHash old_engines; - if(first) { - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); - QStringList known_engines = settings.value(QString::fromUtf8("SearchEngines/knownEngines"), QStringList()).toStringList(); - QVariantList enabled = settings.value(QString::fromUtf8("SearchEngines/knownEnginesEnabled"), QList()).toList(); - Q_ASSERT(known_engines.size() == enabled.size()); - unsigned int nbKnownEngines = known_engines.size(); - for(unsigned int i=0; isetData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath))); - } else { - iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+id+".ico"; - if(QFile::exists(iconPath)) { // ICO support - 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(installed_engines.value(id, true)) - setRowColor(i, "green"); - else - setRowColor(i, "red"); - ++i; - } -} - QList engineSelectDlg::findItemsWithUrl(QString url){ QList res; for(int i=0; itopLevelItemCount(); ++i) { @@ -490,40 +374,40 @@ void engineSelectDlg::installPlugin(QString path, QString plugin_name) { 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.toLocal8Bit().data())); return; } - // Process with install + // 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)) { - // Backup in case install fails + // Backup in case install fails QFile::copy(dest_path, dest_path+".bak"); QFile::remove(dest_path); update = true; } - // Copy the plugin + // Copy the plugin QFile::copy(path, dest_path); - // Check if this was correctly installed - if(!checkInstalled(plugin_name)) { + // Update supported plugins + supported_engines->update(); + // Check if this was correctly installed + if(!supported_engines->contains(plugin_name)) { if(update) { - // Remove broken file + // Remove broken file QFile::remove(dest_path); - // restore backup + // restore backup QFile::copy(dest_path+".bak", dest_path); QFile::remove(dest_path+".bak"); QMessageBox::warning(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("%1 search engine plugin could not be updated, keeping old version.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); return; } else { - // Remove broken file + // Remove broken file QFile::remove(dest_path); QMessageBox::warning(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("%1 search engine plugin could not be installed.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); return; } } - // Install was successful, remove backup + // Install was successful, remove backup if(update) { QFile::remove(dest_path+".bak"); } - // Refresh plugin list - loadSupportedSearchEngines(); if(update) { QMessageBox::information(this, tr("Search plugin install")+" -- "+tr("qBittorrent"), tr("%1 search engine plugin was successfully updated.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); return; @@ -533,175 +417,212 @@ void engineSelectDlg::installPlugin(QString path, QString plugin_name) { } } -void engineSelectDlg::on_installButton_clicked() { - pluginSourceDlg *dlg = new pluginSourceDlg(this); - connect(dlg, SIGNAL(askForLocalFile()), this, SLOT(askForLocalPlugin())); - connect(dlg, SIGNAL(askForUrl()), this, SLOT(askForPluginUrl())); -} - -void engineSelectDlg::askForPluginUrl() { - bool ok; - QString url = QInputDialog::getText(this, tr("New search engine plugin URL"), - tr("URL:"), QLineEdit::Normal, - "http://", &ok); - if (ok && !url.isEmpty()) - downloader->downloadUrl(url); -} - -void engineSelectDlg::askForLocalPlugin() { - QStringList pathsList = QFileDialog::getOpenFileNames(0, - tr("Select search plugins"), QDir::homePath(), -#ifdef HAVE_ZZIP - tr("qBittorrent search plugins")+QString::fromUtf8(" (*.py *.zip)")); -#else - tr("qBittorrent search plugins")+QString::fromUtf8(" (*.py)")); -#endif - QString path; - foreach(path, pathsList) { - if(path.endsWith(".py", Qt::CaseInsensitive)) { - QString plugin_name = path.split(QDir::separator()).last(); - plugin_name.replace(".py", "", Qt::CaseInsensitive); - installPlugin(path, plugin_name); - } -#ifdef HAVE_ZZIP - else { - if(path.endsWith(".zip", Qt::CaseInsensitive)) { - installZipPlugin(path); - } - } -#endif +void engineSelectDlg::loadSupportedSearchEngines() { + // Some clean up first + pluginsTree->clear(); + foreach(QString name, supported_engines->keys()) { + addNewEngine(name); } } -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; +void engineSelectDlg::addNewEngine(QString engine_name) { + QTreeWidgetItem *item = new QTreeWidgetItem(pluginsTree); + SupportedEngine *engine = supported_engines->value(engine_name); + item->setText(ENGINE_NAME, engine->getFullName()); + item->setText(ENGINE_URL, engine->getUrl()); + item->setText(ENGINE_ID, engine->getName()); + if(engine->isEnabled()) { + item->setText(ENGINE_STATE, tr("Yes")); + setRowColor(pluginsTree->indexOfTopLevelItem(item), "green"); + } else { + item->setText(ENGINE_STATE, tr("No")); + setRowColor(pluginsTree->indexOfTopLevelItem(item), "red"); } - bool updated = false; - while(!versions.atEnd()) { - QByteArray line = versions.readLine(); - line.replace("\n", ""); - line = line.trimmed(); - if(line.isEmpty()) continue; - if(line.startsWith("#")) continue; - QList 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 ':' + // Handle icon + QString iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+engine->getName()+".png"; + if(QFile::exists(iconPath)) { + // Good, we already have the icon + item->setData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath))); + } else { + iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+engine->getName()+".ico"; + if(QFile::exists(iconPath)) { // ICO support + item->setData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath))); + } else { + // Icon is missing, we must download it + downloader->downloadUrl(engine->getUrl()+"/favicon.ico"); + } + } +} + + void engineSelectDlg::on_installButton_clicked() { + pluginSourceDlg *dlg = new pluginSourceDlg(this); + connect(dlg, SIGNAL(askForLocalFile()), this, SLOT(askForLocalPlugin())); + connect(dlg, SIGNAL(askForUrl()), this, SLOT(askForPluginUrl())); + } + + void engineSelectDlg::askForPluginUrl() { bool ok; - float version = list.last().toFloat(&ok); - qDebug("read line %s: %.2f", plugin_name.toLocal8Bit().data(), version); - if(!ok) continue; - file_correct = true; - if(isUpdateNeeded(plugin_name, version)) { - qDebug("Plugin: %s is outdated", plugin_name.toLocal8Bit().data()); - // Downloading update - downloader->downloadUrl(updateServer+plugin_name+".pyqBT"); // Actually this is really a .py - downloader->downloadUrl(updateServer+plugin_name+".png"); - updated = true; - }else { - qDebug("Plugin: %s is up to date", plugin_name.toLocal8Bit().data()); + QString url = QInputDialog::getText(this, tr("New search engine plugin URL"), + tr("URL:"), QLineEdit::Normal, + "http://", &ok); + if (ok && !url.isEmpty()) + downloader->downloadUrl(url); + } + + void engineSelectDlg::askForLocalPlugin() { + QStringList pathsList = QFileDialog::getOpenFileNames(0, + tr("Select search plugins"), QDir::homePath(), +#ifdef HAVE_ZZIP + tr("qBittorrent search plugins")+QString::fromUtf8(" (*.py *.zip)")); +#else + tr("qBittorrent search plugins")+QString::fromUtf8(" (*.py)")); +#endif + QString path; + foreach(path, pathsList) { + if(path.endsWith(".py", Qt::CaseInsensitive)) { + QString plugin_name = path.split(QDir::separator()).last(); + plugin_name.replace(".py", "", Qt::CaseInsensitive); + installPlugin(path, plugin_name); + } +#ifdef HAVE_ZZIP + else { + if(path.endsWith(".zip", Qt::CaseInsensitive)) { + installZipPlugin(path); + } + } +#endif } } - // Close file - versions.close(); - // Clean up tmp file - QFile::remove(versions_file); - if(file_correct && !updated) { - QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("All your plugins are already up to date.")); - } - return file_correct; -} -void engineSelectDlg::processDownloadedFile(QString url, QString filePath) { - qDebug("engineSelectDlg received %s", url.toLocal8Bit().data()); - if(url.endsWith("favicon.ico", Qt::CaseInsensitive)){ - // Icon downloaded - QImage fileIcon; - if(fileIcon.load(filePath)) { - QList items = findItemsWithUrl(url); - QTreeWidgetItem *item; - foreach(item, items){ - QString id = item->text(ENGINE_ID); - QString iconPath; - QFile icon(filePath); - icon.open(QIODevice::ReadOnly); - if(ICOHandler::canRead(&icon)) - iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+id+".ico"; - else - iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+id+".png"; - QFile::copy(filePath, iconPath); - item->setData(ENGINE_NAME, Qt::DecorationRole, QVariant(QIcon(iconPath))); + 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; + } + bool updated = false; + while(!versions.atEnd()) { + QByteArray line = versions.readLine(); + line.replace("\n", ""); + line = line.trimmed(); + if(line.isEmpty()) continue; + if(line.startsWith("#")) continue; + QList 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.toLocal8Bit().data(), version); + if(!ok) continue; + file_correct = true; + if(isUpdateNeeded(plugin_name, version)) { + qDebug("Plugin: %s is outdated", plugin_name.toLocal8Bit().data()); + // Downloading update + downloader->downloadUrl(updateServer+plugin_name+".pyqBT"); // Actually this is really a .py + downloader->downloadUrl(updateServer+plugin_name+".png"); + updated = true; + }else { + qDebug("Plugin: %s is up to date", plugin_name.toLocal8Bit().data()); } } - // Delete tmp file - QFile::remove(filePath); - return; + // Close file + versions.close(); + // Clean up tmp file + QFile::remove(versions_file); + if(file_correct && !updated) { + QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("All your plugins are already up to date.")); + } + return file_correct; } - if(url == "http://www.dchris.eu/search_engine2/versions.txt") { - if(!parseVersionsFile(filePath, "http://www.dchris.eu/search_engine2/")) { + + void engineSelectDlg::processDownloadedFile(QString url, QString filePath) { + qDebug("engineSelectDlg received %s", url.toLocal8Bit().data()); + if(url.endsWith("favicon.ico", Qt::CaseInsensitive)){ + // Icon downloaded + QImage fileIcon; + if(fileIcon.load(filePath)) { + QList items = findItemsWithUrl(url); + QTreeWidgetItem *item; + foreach(item, items){ + QString id = item->text(ENGINE_ID); + QString iconPath; + QFile icon(filePath); + icon.open(QIODevice::ReadOnly); + if(ICOHandler::canRead(&icon)) + iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+id+".ico"; + else + iconPath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator()+id+".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_engine2/versions.txt") { + if(!parseVersionsFile(filePath, "http://www.dchris.eu/search_engine2/")) { + qDebug("Primary update server failed, try secondary"); + downloader->downloadUrl("http://hydr0g3n.free.fr/search_engine2/versions.txt"); + } + QFile::remove(filePath); + return; + } + if(url == "http://hydr0g3n.free.fr/search_engine2/versions.txt") { + if(!parseVersionsFile(filePath, "http://hydr0g3n.free.fr/search_engine2/")) { + QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, update server is temporarily unavailable.")); + } + QFile::remove(filePath); + return; + } + if(url.endsWith(".pyqBT", Qt::CaseInsensitive) || url.endsWith(".py", Qt::CaseInsensitive)) { + QString plugin_name = url.split('/').last(); + plugin_name.replace(".pyqBT", ""); + plugin_name.replace(".py", ""); + installPlugin(filePath, plugin_name); + QFile::remove(filePath); + return; + } +#ifdef HAVE_ZZIP + if(url.endsWith(".zip", Qt::CaseInsensitive)) { + installZipPlugin(filePath); + QFile::remove(filePath); + return; + } +#endif + } + + void engineSelectDlg::handleDownloadFailure(QString url, QString reason) { + if(url.endsWith("favicon.ico", Qt::CaseInsensitive)){ + qDebug("Could not download favicon: %s, reason: %s", url.toLocal8Bit().data(), reason.toLocal8Bit().data()); + return; + } + if(url == "http://www.dchris.eu/search_engine2/versions.txt") { + // Primary update server failed, try secondary qDebug("Primary update server failed, try secondary"); downloader->downloadUrl("http://hydr0g3n.free.fr/search_engine2/versions.txt"); + return; } - QFile::remove(filePath); - return; - } - if(url == "http://hydr0g3n.free.fr/search_engine2/versions.txt") { - if(!parseVersionsFile(filePath, "http://hydr0g3n.free.fr/search_engine2/")) { + if(url == "http://hydr0g3n.free.fr/search_engine2/versions.txt") { QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, update server is temporarily unavailable.")); + return; + } + if(url.endsWith(".pyqBT", Qt::CaseInsensitive) || url.endsWith(".py", Qt::CaseInsensitive)) { + // a plugin update download has been failed + QString plugin_name = url.split('/').last(); + plugin_name.replace(".pyqBT", "", Qt::CaseInsensitive); + plugin_name.replace(".py", "", Qt::CaseInsensitive); + QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, %1 search plugin install failed.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); } - QFile::remove(filePath); - return; - } - if(url.endsWith(".pyqBT", Qt::CaseInsensitive) || url.endsWith(".py", Qt::CaseInsensitive)) { - QString plugin_name = url.split('/').last(); - plugin_name.replace(".pyqBT", ""); - plugin_name.replace(".py", ""); - installPlugin(filePath, plugin_name); - QFile::remove(filePath); - return; - } #ifdef HAVE_ZZIP - if(url.endsWith(".zip", Qt::CaseInsensitive)) { - installZipPlugin(filePath); - QFile::remove(filePath); - return; - } + if(url.endsWith(".zip", Qt::CaseInsensitive)) { + QString plugin_name = url.split('/').last(); + plugin_name.replace(".zip", "", Qt::CaseInsensitive); + QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, %1 search plugin install failed.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); + } #endif -} - -void engineSelectDlg::handleDownloadFailure(QString url, QString reason) { - if(url.endsWith("favicon.ico", Qt::CaseInsensitive)){ - qDebug("Could not download favicon: %s, reason: %s", url.toLocal8Bit().data(), reason.toLocal8Bit().data()); - return; } - if(url == "http://www.dchris.eu/search_engine2/versions.txt") { - // Primary update server failed, try secondary - qDebug("Primary update server failed, try secondary"); - downloader->downloadUrl("http://hydr0g3n.free.fr/search_engine2/versions.txt"); - return; - } - if(url == "http://hydr0g3n.free.fr/search_engine2/versions.txt") { - QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, update server is temporarily unavailable.")); - return; - } - if(url.endsWith(".pyqBT", Qt::CaseInsensitive) || url.endsWith(".py", Qt::CaseInsensitive)) { - // a plugin update download has been failed - QString plugin_name = url.split('/').last(); - plugin_name.replace(".pyqBT", "", Qt::CaseInsensitive); - plugin_name.replace(".py", "", Qt::CaseInsensitive); - QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, %1 search plugin install failed.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); - } -#ifdef HAVE_ZZIP - if(url.endsWith(".zip", Qt::CaseInsensitive)) { - QString plugin_name = url.split('/').last(); - plugin_name.replace(".zip", "", Qt::CaseInsensitive); - QMessageBox::warning(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), tr("Sorry, %1 search plugin install failed.", "%1 is the name of the search engine").arg(plugin_name.toLocal8Bit().data())); - } -#endif -} diff --git a/src/engineSelectDlg.h b/src/engineSelectDlg.h index 4097fe818..c15cc6f19 100644 --- a/src/engineSelectDlg.h +++ b/src/engineSelectDlg.h @@ -32,6 +32,7 @@ #define ENGINE_SELECT_DLG_H #include "ui_engineSelect.h" +#include "supportedEngines.h" class downloadThread; class QDropEvent; @@ -40,12 +41,11 @@ class engineSelectDlg : public QDialog, public Ui::engineSelect{ Q_OBJECT private: - // Search related - QHash installed_engines; downloadThread *downloader; + SupportedEngines *supported_engines; public: - engineSelectDlg(QWidget *parent); + engineSelectDlg(QWidget *parent, SupportedEngines *supported_engines); ~engineSelectDlg(); QList findItemsWithUrl(QString url); QTreeWidgetItem* findItemWithID(QString id); @@ -53,15 +53,14 @@ class engineSelectDlg : public QDialog, public Ui::engineSelect{ protected: bool parseVersionsFile(QString versions_file, QString updateServer); bool isUpdateNeeded(QString plugin_name, float new_version) const; - bool checkInstalled(QString plugin_name) const; signals: void enginesChanged(); protected slots: - void saveSettings(); void on_closeButton_clicked(); - void loadSupportedSearchEngines(bool first=false); + void loadSupportedSearchEngines(); + void addNewEngine(QString engine_name); void toggleEngineState(QTreeWidgetItem*, int); void setRowColor(int row, QString color); void processDownloadedFile(QString url, QString filePath); diff --git a/src/icons.qrc b/src/icons.qrc index 61c379a9f..04f9c999c 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1,7 +1,6 @@ Icons/rss32.png - Icons/money.png Icons/sphere2.png Icons/downarrow.png Icons/url.png @@ -95,6 +94,7 @@ Icons/oxygen/bt_settings.png Icons/oxygen/document-new.png Icons/oxygen/tab-close.png + Icons/oxygen/wallet.png Icons/oxygen/webui.png Icons/oxygen/list-remove.png Icons/oxygen/connection.png diff --git a/src/search.ui b/src/search.ui index a1cba6a6f..0abf5569e 100644 --- a/src/search.ui +++ b/src/search.ui @@ -30,53 +30,7 @@ - - - - All categories - - - - - Movies - - - - - TV shows - - - - - Music - - - - - Games - - - - - Anime - - - - - Software - - - - - Pictures - - - - - Books - - - + diff --git a/src/searchEngine.cpp b/src/searchEngine.cpp index 343a30caf..4ab47f1a3 100644 --- a/src/searchEngine.cpp +++ b/src/searchEngine.cpp @@ -76,27 +76,43 @@ SearchEngine::SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, b connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tab_changed(int))); searchTimeout = new QTimer(this); searchTimeout->setSingleShot(true); - connect(searchTimeout, SIGNAL(timeout()), this, SLOT(on_stop_search_button_clicked())); - // Check last enabled search engines - loadEngineSettings(); + connect(searchTimeout, SIGNAL(timeout()), this, SLOT(on_search_button_clicked())); // Update nova.py search plugin if necessary updateNova(); + supported_engines = new SupportedEngines(); + // Fill in category combobox + fillCatCombobox(); connect(search_pattern, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(displayPatternContextMenu(QPoint))); } +void SearchEngine::fillCatCombobox() { + comboCategory->clear(); + comboCategory->addItem(full_cat_names["all"], QVariant("all")); + QStringList supported_cat = supported_engines->supportedCategories(); + foreach(QString cat, supported_cat) { + qDebug("Supported category: %s", cat.toLocal8Bit().data()); + comboCategory->addItem(full_cat_names[cat], QVariant(cat)); + } +} + +QString SearchEngine::selectedCategory() const { + return comboCategory->itemData(comboCategory->currentIndex()).toString(); +} + SearchEngine::~SearchEngine(){ qDebug("Search destruction"); // save the searchHistory for later uses saveSearchHistory(); searchProcess->kill(); searchProcess->waitForFinished(); - foreach(QProcess *downloader, downloaders) { - downloader->kill(); - downloader->waitForFinished(); - delete downloader; - } + foreach(QProcess *downloader, downloaders) { + downloader->kill(); + downloader->waitForFinished(); + delete downloader; + } delete searchTimeout; delete searchProcess; + delete supported_engines; if(searchCompleter) delete searchCompleter; } @@ -145,20 +161,19 @@ void SearchEngine::displayPatternContextMenu(QPoint) { void SearchEngine::tab_changed(int t) {//when we switch from a tab that is not empty to another that is empty the download button - //doesn't have to be available - if(t>-1) - {//-1 = no more tab + //doesn't have to be available + if(t>-1) + {//-1 = no more tab if(all_tab.at(tabWidget->currentIndex())->getCurrentSearchListModel()->rowCount()) { - download_button->setEnabled(true); + download_button->setEnabled(true); } else { - download_button->setEnabled(false); + download_button->setEnabled(false); } - } + } } void SearchEngine::on_enginesButton_clicked() { - engineSelectDlg *dlg = new engineSelectDlg(this); - connect(dlg, SIGNAL(enginesChanged()), this, SLOT(loadEngineSettings())); + new engineSelectDlg(this, supported_engines); } // get the last searchs from a QSettings to a QStringList @@ -169,23 +184,6 @@ 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()).toList(); - unsigned int i = 0; - foreach(const QString &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() { @@ -198,14 +196,14 @@ void SearchEngine::saveSearchHistory() // Function called when we click on search button void SearchEngine::on_search_button_clicked(){ if(searchProcess->state() != QProcess::NotRunning){ - searchProcess->kill(); - searchProcess->waitForFinished(); + searchProcess->terminate(); + search_stopped = true; + if(searchTimeout->isActive()) { + searchTimeout->stop(); + } search_button->setText("Search"); return; } - if(searchTimeout->isActive()) { - searchTimeout->stop(); - } QString pattern = search_pattern->text().trimmed(); // No search pattern entered if(pattern.isEmpty()){ @@ -230,12 +228,13 @@ void SearchEngine::on_search_button_clicked(){ } // Getting checked search engines - Q_ASSERT(!enabled_engines.empty()); QStringList params; QStringList engineNames; search_stopped = false; params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py"; - params << enabled_engines.join(","); + params << supported_engines->enginesEnabled().join(","); + qDebug("Search with category: %s", selectedCategory().toLocal8Bit().data()); + params << selectedCategory(); params << pattern.split(" "); // Update SearchEngine widgets no_search_results = true; @@ -293,15 +292,15 @@ void SearchEngine::saveResultsColumnsWidth() { } void SearchEngine::downloadTorrent(QString engine_url, QString torrent_url) { - QProcess *downloadProcess = new QProcess(this); - connect(downloadProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(downloadFinished(int,QProcess::ExitStatus))); - downloaders << downloadProcess; - QStringList params; - params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; - params << engine_url; - params << torrent_url; - // Launch search - downloadProcess->start("python", params, QIODevice::ReadOnly); + QProcess *downloadProcess = new QProcess(this); + connect(downloadProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(downloadFinished(int,QProcess::ExitStatus))); + downloaders << downloadProcess; + QStringList params; + params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; + params << engine_url; + params << torrent_url; + // Launch search + downloadProcess->start("python", params, QIODevice::ReadOnly); } void SearchEngine::searchStarted(){ @@ -316,7 +315,7 @@ void SearchEngine::downloadSelectedItem(const QModelIndex& index){ int row = index.row(); // Get Item url QStandardItemModel *model = all_tab.at(tabWidget->currentIndex())->getCurrentSearchListModel(); - QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); + QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); QString torrent_url = model->data(model->index(index.row(), URL_COLUMN)).toString(); // Download from url downloadTorrent(engine_url, torrent_url); @@ -343,19 +342,19 @@ void SearchEngine::readSearchOutput(){ } void SearchEngine::downloadFinished(int exitcode, QProcess::ExitStatus) { - QProcess *downloadProcess = (QProcess*)sender(); - if(exitcode == 0) { - QString line = QString::fromUtf8(downloadProcess->readAllStandardOutput()).trimmed(); - QStringList parts = line.split(' '); - if(parts.size() == 2) { - QString path = parts[0]; - QString url = parts[1]; - BTSession->processDownloadedFile(url, path); - } - } - qDebug("Deleting downloadProcess"); - downloaders.removeAll(downloadProcess); - delete downloadProcess; + QProcess *downloadProcess = (QProcess*)sender(); + if(exitcode == 0) { + QString line = QString::fromUtf8(downloadProcess->readAllStandardOutput()).trimmed(); + QStringList parts = line.split(' '); + if(parts.size() == 2) { + QString path = parts[0]; + QString url = parts[1]; + BTSession->processDownloadedFile(url, path); + } + } + qDebug("Deleting downloadProcess"); + downloaders.removeAll(downloadProcess); + delete downloadProcess; } // Update nova.py search plugin if necessary @@ -386,8 +385,8 @@ void SearchEngine::updateNova() { QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py").setPermissions(perm); - filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; - if(misc::getPluginVersion(":/search_engine/nova2dl.py") > misc::getPluginVersion(filePath)) { + filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; + if(misc::getPluginVersion(":/search_engine/nova2dl.py") > misc::getPluginVersion(filePath)) { if(QFile::exists(filePath)){ QFile::remove(filePath); } @@ -409,7 +408,7 @@ void SearchEngine::updateNova() { } QFile::copy(":/search_engine/helpers.py", filePath); } - QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"helpers.py").setPermissions(perm); + QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"helpers.py").setPermissions(perm); QString destDir = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator(); QDir shipped_subDir(":/search_engine/engines/"); QStringList files = shipped_subDir.entryList(); @@ -462,6 +461,9 @@ void SearchEngine::searchFinished(int exitcode,QProcess::ExitStatus){ if(currentSearchTab) currentSearchTab->getCurrentLabel()->setText(tr("Results", "i.e: Search results")+QString::fromUtf8(" (")+misc::toQString(nb_search_results)+QString::fromUtf8("):")); search_button->setText("Search"); + if(searchTimeout->isActive()) { + searchTimeout->stop(); + } } // SLOT to append one line to search results list @@ -491,34 +493,26 @@ void SearchEngine::appendSearchResult(QString line){ download_button->setEnabled(true); } -// Stop search while it is working in background -void SearchEngine::on_stop_search_button_clicked(){ - // Kill process - searchProcess->terminate(); - search_stopped = true; - searchTimeout->stop(); -} - // Clear search results list void SearchEngine::closeTab_button_clicked(){ if(all_tab.size()) { qDebug("currentTab rank: %d", tabWidget->currentIndex()); qDebug("currentSearchTab rank: %d", tabWidget->indexOf(currentSearchTab)); if(tabWidget->currentIndex() == tabWidget->indexOf(currentSearchTab)) { - qDebug("Deleted current search Tab"); - if(searchProcess->state() != QProcess::NotRunning){ - searchProcess->terminate(); - } - if(searchTimeout->isActive()) { - searchTimeout->stop(); - } - search_stopped = true; - currentSearchTab = 0; + qDebug("Deleted current search Tab"); + if(searchProcess->state() != QProcess::NotRunning){ + searchProcess->terminate(); + } + if(searchTimeout->isActive()) { + searchTimeout->stop(); + } + search_stopped = true; + currentSearchTab = 0; } delete all_tab.takeAt(tabWidget->currentIndex()); if(!all_tab.size()) { - closeTab_button->setEnabled(false); - download_button->setEnabled(false); + closeTab_button->setEnabled(false); + download_button->setEnabled(false); } } } @@ -532,7 +526,7 @@ void SearchEngine::on_download_button_clicked(){ // Get Item url QStandardItemModel *model = all_tab.at(tabWidget->currentIndex())->getCurrentSearchListModel(); QString torrent_url = model->data(model->index(index.row(), URL_COLUMN)).toString(); - QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); + QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); downloadTorrent(engine_url, torrent_url); all_tab.at(tabWidget->currentIndex())->setRowColor(index.row(), "red"); } diff --git a/src/searchEngine.h b/src/searchEngine.h index 17a5dd495..a3f96bc27 100644 --- a/src/searchEngine.h +++ b/src/searchEngine.h @@ -40,6 +40,7 @@ #include "ui_search.h" #include "engineSelectDlg.h" #include "SearchTab.h" +#include "supportedEngines.h" class bittorrent; class QSystemTrayIcon; @@ -50,52 +51,55 @@ class SearchEngine; class SearchEngine : public QWidget, public Ui::search_engine{ Q_OBJECT - private: - // Search related - QProcess *searchProcess; - QList downloaders; - bool search_stopped; - bool no_search_results; - QByteArray search_result_line_truncated; - unsigned long nb_search_results; - QPointer searchCompleter; - QStringList searchHistory; - bittorrent *BTSession; - QSystemTrayIcon *myTrayIcon; - bool systrayIntegration; - QStringList enabled_engines; - QTimer *searchTimeout; - SearchTab *currentSearchTab; - QPushButton *closeTab_button; - QList all_tab; // To store all tabs - public: - SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration); - ~SearchEngine(); - float getPluginVersion(QString filePath) const; - public slots: - void on_download_button_clicked(); - void downloadSelectedItem(const QModelIndex& index); - protected slots: - // Search slots - void tab_changed(int);//to prevent the use of the download button when the tab is empty - void on_search_button_clicked(); - void on_stop_search_button_clicked(); - void closeTab_button_clicked(); - void appendSearchResult(QString line); - void searchFinished(int exitcode,QProcess::ExitStatus); - void readSearchOutput(); - void loadEngineSettings(); - void searchStarted(); - void startSearchHistory(); - void updateNova(); - void saveSearchHistory(); - void on_enginesButton_clicked(); - void propagateSectionResized(int index, int oldsize , int newsize); - void saveResultsColumnsWidth(); - void downloadFinished(int exitcode, QProcess::ExitStatus); - void downloadTorrent(QString engine_url, QString torrent_url); - void displayPatternContextMenu(QPoint); - void createCompleter(); +private: + // Search related + QProcess *searchProcess; + QList downloaders; + bool search_stopped; + bool no_search_results; + QByteArray search_result_line_truncated; + unsigned long nb_search_results; + QPointer searchCompleter; + QStringList searchHistory; + bittorrent *BTSession; + QSystemTrayIcon *myTrayIcon; + bool systrayIntegration; + SupportedEngines *supported_engines; + QTimer *searchTimeout; + SearchTab *currentSearchTab; + QPushButton *closeTab_button; + QList all_tab; // To store all tabs + const SearchCategories full_cat_names; +public: + SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon, bool systrayIntegration); + ~SearchEngine(); + float getPluginVersion(QString filePath) const; + QString selectedCategory() const; + +public slots: + void on_download_button_clicked(); + void downloadSelectedItem(const QModelIndex& index); + +protected slots: + // Search slots + void tab_changed(int);//to prevent the use of the download button when the tab is empty + void on_search_button_clicked(); + void closeTab_button_clicked(); + void appendSearchResult(QString line); + void searchFinished(int exitcode,QProcess::ExitStatus); + void readSearchOutput(); + void searchStarted(); + void startSearchHistory(); + void updateNova(); + void saveSearchHistory(); + void on_enginesButton_clicked(); + void propagateSectionResized(int index, int oldsize , int newsize); + void saveResultsColumnsWidth(); + void downloadFinished(int exitcode, QProcess::ExitStatus); + void downloadTorrent(QString engine_url, QString torrent_url); + void displayPatternContextMenu(QPoint); + void createCompleter(); + void fillCatCombobox(); }; #endif diff --git a/src/search_engine/engines/mininova.py b/src/search_engine/engines/mininova.py index 3225159d0..14ed946f7 100644 --- a/src/search_engine/engines/mininova.py +++ b/src/search_engine/engines/mininova.py @@ -1,4 +1,4 @@ -#VERSION: 1.23 +#VERSION: 1.31 #AUTHORS: Fabien Devaux (fab@gnux.info) # Redistribution and use in source and binary forms, with or without @@ -31,14 +31,15 @@ from xml.dom import minidom import re class mininova(object): + # Mandatory properties url = 'http://www.mininova.org' name = 'Mininova' - table_items = 'added cat name size seeds leech'.split() + supported_categories = {'all': '0', 'movies': '4', 'tv': '8', 'music': '5', 'games': '3', 'anime': '1', 'software': '7', 'pictures': '6', 'books': '2'} def download_torrent(self, info): print download_file(info) - def search(self, what): + def search(self, what, cat='all'): def get_link(lnk): lnks = lnk.getElementsByTagName('a') @@ -71,10 +72,15 @@ class mininova(object): return txt.toxml() else: return ''.join([ get_text(n) for n in txt.childNodes]) + + if cat == 'all': + self.table_items = 'added cat name size seeds leech'.split() + else: + self.table_items = 'added name size seeds leech'.split() page = 1 while True and page<11: res = 0 - dat = retrieve_url(self.url+'/search/%s/seeds/%d'%(what, page)) + dat = retrieve_url(self.url+'/search/%s/%s/seeds/%d'%(what, self.supported_categories[cat], page)) dat = re.sub(" @@ -43,6 +43,7 @@ import os import glob THREADED = True +CATEGORIES = ('all', 'movies', 'tv', 'music', 'games', 'anime', 'software', 'pictures', 'books') ################################################################################ # Every engine should have a "search" method taking @@ -65,48 +66,90 @@ for engine in engines: except: pass +def engineToXml(short_name): + xml = "<%s>\n"%short_name + exec "engine = %s()"%short_name + xml += "%s\n"%engine.name + xml += "%s\n"%engine.url + xml += "" + if hasattr(engine, 'supported_categories'): + supported_categories = engine.supported_categories.keys() + supported_categories.remove('all') + xml += " ".join(supported_categories) + xml += "\n" + xml += "\n"%short_name + return xml + +def displayCapabilities(): + """ + Display capabilities in XML format + + + long name + http://example.com + movies music games + + + """ + xml = "" + for short_name in supported_engines: + xml += engineToXml(short_name) + xml += "" + print xml + class EngineLauncher(threading.Thread): - def __init__(self, engine, what): + def __init__(self, engine, what, cat='all'): threading.Thread.__init__(self) self.engine = engine self.what = what + self.cat = cat def run(self): - self.engine.search(self.what) + if hasattr(self.engine, 'supported_categories'): + if self.cat == 'all' or self.cat in self.engine.supported_categories.keys(): + self.engine.search(self.what, self.cat) + elif self.cat == 'all': + self.engine.search(self.what) if __name__ == '__main__': if len(sys.argv) < 2: - raise SystemExit('./nova2.py [all|engine1[,engine2]*] \navailable engines: %s'% + raise SystemExit('./nova2.py [all|engine1[,engine2]*] \navailable engines: %s'% (','.join(supported_engines))) if len(sys.argv) == 2: - 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) + if sys.argv[1] == "--capabilities": + displayCapabilities() sys.exit(0) else: - raise SystemExit('./nova.py [all|engine1[,engine2]*] \navailable engines: %s'% + raise SystemExit('./nova.py [all|engine1[,engine2]*] \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:]) + + cat = sys.argv[2].lower() + + if cat not in CATEGORIES: + raise SystemExit('Invalid category!') + + what = '+'.join(sys.argv[3:]) + threads = [] for engine in engines_list: try: if THREADED: - exec "l = EngineLauncher(%s(), what)" % engine + exec "l = EngineLauncher(%s(), what, cat)"%engine threads.append(l) l.start() else: - engine().search(what) + exec "e = %s()"%engine + if hasattr(engine, 'supported_categories'): + if cat == 'all' or cat in e.supported_categories.keys(): + e.search(what, cat) + elif self.cat == 'all': + e.search(what) + engine().search(what, cat) except: pass if THREADED: diff --git a/src/seeding.ui b/src/seeding.ui index e22da1b93..6cf5f042b 100644 --- a/src/seeding.ui +++ b/src/seeding.ui @@ -118,7 +118,7 @@ - :/Icons/money.png:/Icons/money.png + :/Icons/oxygen/wallet.png:/Icons/oxygen/wallet.png Buy it diff --git a/src/src.pro b/src/src.pro index 4631eab53..b0e1929cb 100644 --- a/src/src.pro +++ b/src/src.pro @@ -185,7 +185,8 @@ HEADERS += GUI.h \ stacktrace.h \ torrentPersistentData.h \ FeedDownloader.h \ - feedList.h + feedList.h \ + supportedEngines.h FORMS += MainWindow.ui \ options.ui \ about.ui \ diff --git a/src/supportedEngines.h b/src/supportedEngines.h new file mode 100644 index 000000000..e07283f90 --- /dev/null +++ b/src/supportedEngines.h @@ -0,0 +1,173 @@ +/* + * 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. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#ifndef SEARCHENGINES_H +#define SEARCHENGINES_H + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" + +class SearchCategories: public QObject, public QHash { + Q_OBJECT + +public: + SearchCategories() { + (*this)["all"] = tr("All categories"); + (*this)["movies"] = tr("Movies"); + (*this)["tv"] = tr("TV shows"); + (*this)["music"] = tr("Music"); + (*this)["games"] = tr("Games"); + (*this)["anime"] = tr("Anime"); + (*this)["software"] = tr("Software"); + (*this)["pictures"] = tr("Pictures"); + (*this)["books"] = tr("Books"); + } +}; + +class SupportedEngine { +private: + QString name; + QString full_name; + QString url; + QStringList supported_categories; + bool enabled; + +public: + SupportedEngine(QDomElement engine_elem) { + name = engine_elem.tagName(); + full_name = engine_elem.elementsByTagName("name").at(0).toElement().text(); + url = engine_elem.elementsByTagName("url").at(0).toElement().text(); + supported_categories = engine_elem.elementsByTagName("categories").at(0).toElement().text().split(" "); + QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + QVariantList disabled_engines = settings.value(QString::fromUtf8("SearchEngines/disabledEngines"), QVariantList()).toList(); + enabled = !disabled_engines.contains(QVariant(name)); + } + + QString getName() const { return name; } + QString getUrl() const { return url; } + QString getFullName() const { return full_name; } + QStringList getSupportedCategories() const { return supported_categories; } + bool isEnabled() const { return enabled; } + void setEnabled(bool _enabled) { + enabled = _enabled; + // Save to Hard disk + QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + QVariantList disabled_engines = settings.value(QString::fromUtf8("SearchEngines/disabledEngines"), QVariantList()).toList(); + if(enabled) { + disabled_engines.removeAll(QVariant(name)); + } else { + disabled_engines.append(QVariant(name)); + } + settings.setValue("SearchEngines/disabledEngines", disabled_engines); + } +}; + +class SupportedEngines: public QObject, public QHash { + Q_OBJECT + +signals: + void newSupportedEngine(QString name); + +public: + SupportedEngines() { + update(); + } + + ~SupportedEngines() { + qDeleteAll(this->values()); + } + + QStringList enginesEnabled() const { + QStringList engines; + foreach(SupportedEngine *engine, values()) { + if(engine->isEnabled()) + engines << engine->getName(); + } + return engines; + } + + QStringList supportedCategories() const { + QStringList supported_cat; + foreach(SupportedEngine *engine, values()) { + QStringList s = engine->getSupportedCategories(); + foreach(QString cat, s) { + cat = cat.trimmed(); + if(!cat.isEmpty() && !supported_cat.contains(cat)) + supported_cat << cat; + } + } + return supported_cat; + } + +public slots: + void update() { + QProcess nova; + QStringList params; + params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py"; + params << "--capabilities"; + nova.start("python", params, QIODevice::ReadOnly); + nova.waitForStarted(); + nova.waitForFinished(); + QString capabilities = QString(nova.readAll()); + QDomDocument xml_doc; + if(!xml_doc.setContent(capabilities)) { + std::cerr << "Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data() << std::endl; + return; + } + QDomElement root = xml_doc.documentElement(); + if(root.tagName() != "capabilities") { + std::cout << "Invalid XML file for Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data() << std::endl; + return; + } + for(QDomNode engine_node = root.firstChild(); !engine_node.isNull(); engine_node = engine_node.nextSibling()) { + QDomElement engine_elem = engine_node.toElement(); + if(!engine_elem.isNull()) { + SupportedEngine *s = new SupportedEngine(engine_elem); + if(this->contains(s->getName())) { + // Already in the list + delete s; + } else { + qDebug("Supported search engine: %s", s->getFullName().toLocal8Bit().data()); + (*this)[s->getName()] = s; + emit newSupportedEngine(s->getName()); + } + } + } + } +}; + +#endif // SEARCHENGINES_H