Browse Source

Improve Search handling

Legacy SearchEngine class really has three roles:
  1. Manage search plugins,
  2. Handle the search job, and
  3. Handle the download of the torrent file using the search plugin.
Now it is splitted into 3 classes: SearchManager, SearchHandler and
SearchDownloadHandler.
Search GUI is also improved.
adaptive-webui-19844
Vladimir Golovnev (Glassez) 7 years ago
parent
commit
f3d370870d
No known key found for this signature in database
GPG Key ID: 52A2C7DEE2DFA6F7
  1. 8
      src/base/CMakeLists.txt
  2. 8
      src/base/base.pri
  3. 66
      src/base/search/searchdownloadhandler.cpp
  4. 53
      src/base/search/searchdownloadhandler.h
  5. 199
      src/base/search/searchhandler.cpp
  6. 90
      src/base/search/searchhandler.h
  7. 282
      src/base/search/searchpluginmanager.cpp
  8. 69
      src/base/search/searchpluginmanager.h
  9. 16
      src/gui/search/pluginselectdlg.cpp
  10. 6
      src/gui/search/pluginselectdlg.h
  11. 255
      src/gui/search/searchtab.cpp
  12. 70
      src/gui/search/searchtab.h
  13. 321
      src/gui/search/searchwidget.cpp
  14. 41
      src/gui/search/searchwidget.h

8
src/base/CMakeLists.txt

@ -44,6 +44,9 @@ rss/rss_feed.h
rss/rss_folder.h rss/rss_folder.h
rss/rss_item.h rss/rss_item.h
rss/rss_session.h rss/rss_session.h
search/searchdownloadhandler.h
search/searchhandler.h
search/searchpluginmanager.h
utils/fs.h utils/fs.h
utils/gzip.h utils/gzip.h
utils/misc.h utils/misc.h
@ -61,7 +64,6 @@ logger.h
preferences.h preferences.h
profile.h profile.h
scanfoldersmodel.h scanfoldersmodel.h
searchengine.h
settingsstorage.h settingsstorage.h
torrentfileguard.h torrentfileguard.h
torrentfilter.h torrentfilter.h
@ -109,6 +111,9 @@ rss/rss_feed.cpp
rss/rss_folder.cpp rss/rss_folder.cpp
rss/rss_item.cpp rss/rss_item.cpp
rss/rss_session.cpp rss/rss_session.cpp
search/searchdownloadhandler.cpp
search/searchhandler.cpp
search/searchpluginmanager.cpp
utils/fs.cpp utils/fs.cpp
utils/gzip.cpp utils/gzip.cpp
utils/misc.cpp utils/misc.cpp
@ -123,7 +128,6 @@ logger.cpp
preferences.cpp preferences.cpp
profile.cpp profile.cpp
scanfoldersmodel.cpp scanfoldersmodel.cpp
searchengine.cpp
settingsstorage.cpp settingsstorage.cpp
torrentfileguard.cpp torrentfileguard.cpp
torrentfilter.cpp torrentfilter.cpp

8
src/base/base.pri

@ -52,7 +52,9 @@ HEADERS += \
$$PWD/rss/rss_item.h \ $$PWD/rss/rss_item.h \
$$PWD/rss/rss_session.h \ $$PWD/rss/rss_session.h \
$$PWD/scanfoldersmodel.h \ $$PWD/scanfoldersmodel.h \
$$PWD/searchengine.h \ $$PWD/search/searchhandler.h \
$$PWD/search/searchdownloadhandler.h \
$$PWD/search/searchpluginmanager.h \
$$PWD/settingsstorage.h \ $$PWD/settingsstorage.h \
$$PWD/settingvalue.h \ $$PWD/settingvalue.h \
$$PWD/torrentfileguard.h \ $$PWD/torrentfileguard.h \
@ -115,7 +117,9 @@ SOURCES += \
$$PWD/rss/rss_item.cpp \ $$PWD/rss/rss_item.cpp \
$$PWD/rss/rss_session.cpp \ $$PWD/rss/rss_session.cpp \
$$PWD/scanfoldersmodel.cpp \ $$PWD/scanfoldersmodel.cpp \
$$PWD/searchengine.cpp \ $$PWD/search/searchdownloadhandler.cpp \
$$PWD/search/searchhandler.cpp \
$$PWD/search/searchpluginmanager.cpp \
$$PWD/settingsstorage.cpp \ $$PWD/settingsstorage.cpp \
$$PWD/torrentfileguard.cpp \ $$PWD/torrentfileguard.cpp \
$$PWD/torrentfilter.cpp \ $$PWD/torrentfilter.cpp \

66
src/base/search/searchdownloadhandler.cpp

@ -0,0 +1,66 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
*
* 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.
*/
#include "searchdownloadhandler.h"
#include <QProcess>
#include "../utils/fs.h"
#include "../utils/misc.h"
#include "searchpluginmanager.h"
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager)
: QObject {manager}
, m_manager {manager}
, m_downloadProcess {new QProcess {this}}
{
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_downloadProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished)
, this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params {
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"),
siteUrl,
url
};
// Launch search
m_downloadProcess->start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
}
void SearchDownloadHandler::downloadProcessFinished(int exitcode)
{
QString path;
if ((exitcode == 0) && (m_downloadProcess->exitStatus() == QProcess::NormalExit)) {
const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed();
const QVector<QStringRef> parts = line.splitRef(' ');
if (parts.size() == 2)
path = parts[0].toString();
}
emit downloadFinished(path);
}

53
src/base/search/searchdownloadhandler.h

@ -0,0 +1,53 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
*
* 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.
*/
#pragma once
#include <QObject>
class QProcess;
class SearchPluginManager;
class SearchDownloadHandler : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(SearchDownloadHandler)
friend class SearchPluginManager;
SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager);
signals:
void downloadFinished(const QString &path);
private:
void downloadProcessFinished(int exitcode);
SearchPluginManager *m_manager;
QProcess *m_downloadProcess;
};

199
src/base/search/searchhandler.cpp

@ -0,0 +1,199 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#include "searchhandler.h"
#include <QProcess>
#include <QTimer>
#include "../utils/fs.h"
#include "../utils/misc.h"
#include "searchpluginmanager.h"
namespace
{
enum SearchResultColumn
{
PL_DL_LINK,
PL_NAME,
PL_SIZE,
PL_SEEDS,
PL_LEECHS,
PL_ENGINE_URL,
PL_DESC_LINK,
NB_PLUGIN_COLUMNS
};
}
SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
: QObject {manager}
, m_pattern {pattern}
, m_category {category}
, m_usedPlugins {usedPlugins}
, m_manager {manager}
, m_searchProcess {new QProcess {this}}
, m_searchTimeout {new QTimer {this}}
{
// Load environment variables (proxy)
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
const QStringList params {
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
m_usedPlugins.join(","),
m_category
};
// Launch search
m_searchProcess->setProgram(Utils::Misc::pythonExecutable());
m_searchProcess->setArguments(params + m_pattern.split(" "));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
#else
connect(m_searchProcess, static_cast<void(QProcess::*)(QProcess::ProcessError)>(&QProcess::error)
, this, &SearchHandler::processFailed);
#endif
connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchHandler::readSearchOutput);
connect(m_searchProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished)
, this, &SearchHandler::processFinished);
m_searchTimeout->setSingleShot(true);
connect(m_searchTimeout, &QTimer::timeout, this, &SearchHandler::cancelSearch);
m_searchTimeout->start(180000); // 3 min
// deferred start allows clients to handle starting-related signals
QTimer::singleShot(0, this, [this]() { m_searchProcess->start(QIODevice::ReadOnly); });
}
bool SearchHandler::isActive() const
{
return (m_searchProcess->state() != QProcess::NotRunning);
}
void SearchHandler::cancelSearch()
{
if ((m_searchProcess->state() == QProcess::NotRunning) || m_searchCancelled)
return;
#ifdef Q_OS_WIN
m_searchProcess->kill();
#else
m_searchProcess->terminate();
#endif
m_searchCancelled = true;
m_searchTimeout->stop();
}
// Slot called when QProcess is Finished
// QProcess can be finished for 3 reasons:
// Error | Stopped by user | Finished normally
void SearchHandler::processFinished(int exitcode)
{
m_searchTimeout->stop();
if (m_searchCancelled)
emit searchFinished(true);
else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0))
emit searchFinished(false);
else
emit searchFailed();
}
// search QProcess return output as soon as it gets new
// stuff to read. We split it into lines and parse each
// line to SearchResult calling parseSearchResult().
void SearchHandler::readSearchOutput()
{
QByteArray output = m_searchProcess->readAllStandardOutput();
output.replace("\r", "");
QList<QByteArray> lines = output.split('\n');
if (!m_searchResultLineTruncated.isEmpty())
lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
m_searchResultLineTruncated = lines.takeLast().trimmed();
QList<SearchResult> searchResultList;
foreach (const QByteArray &line, lines) {
SearchResult searchResult;
if (parseSearchResult(QString::fromUtf8(line), searchResult))
searchResultList << searchResult;
}
if (!searchResultList.isEmpty()) {
m_results.append(searchResultList);
emit newSearchResults(searchResultList);
}
}
void SearchHandler::processFailed()
{
if (!m_searchCancelled)
emit searchFailed();
}
// Parse one line of search results list
// Line is in the following form:
// file url | file name | file size | nb seeds | nb leechers | Search engine url
bool SearchHandler::parseSearchResult(const QString &line, SearchResult &searchResult)
{
const QStringList parts = line.split("|");
const int nbFields = parts.size();
if (nbFields < (NB_PLUGIN_COLUMNS - 1)) return false; // -1 because desc_link is optional
searchResult = SearchResult();
searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed(); // download URL
searchResult.fileName = parts.at(PL_NAME).trimmed(); // Name
searchResult.fileSize = parts.at(PL_SIZE).trimmed().toLongLong(); // Size
bool ok = false;
searchResult.nbSeeders = parts.at(PL_SEEDS).trimmed().toLongLong(&ok); // Seeders
if (!ok || (searchResult.nbSeeders < 0))
searchResult.nbSeeders = -1;
searchResult.nbLeechers = parts.at(PL_LEECHS).trimmed().toLongLong(&ok); // Leechers
if (!ok || (searchResult.nbLeechers < 0))
searchResult.nbLeechers = -1;
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed(); // Search site URL
if (nbFields == NB_PLUGIN_COLUMNS)
searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed(); // Description Link
return true;
}
SearchPluginManager *SearchHandler::manager() const
{
return m_manager;
}
QList<SearchResult> SearchHandler::results() const
{
return m_results;
}
QString SearchHandler::pattern() const
{
return m_pattern;
}

90
src/base/search/searchhandler.h

@ -0,0 +1,90 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#pragma once
#include <QByteArray>
#include <QList>
#include <QObject>
class QProcess;
class QTimer;
struct SearchResult
{
QString fileName;
QString fileUrl;
qlonglong fileSize;
qlonglong nbSeeders;
qlonglong nbLeechers;
QString siteUrl;
QString descrLink;
};
class SearchPluginManager;
class SearchHandler : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(SearchHandler)
friend class SearchPluginManager;
SearchHandler(const QString &pattern, const QString &category
, const QStringList &usedPlugins, SearchPluginManager *manager);
public:
bool isActive() const;
QString pattern() const;
SearchPluginManager *manager() const;
QList<SearchResult> results() const;
void cancelSearch();
signals:
void searchFinished(bool cancelled = false);
void searchFailed();
void newSearchResults(const QList<SearchResult> &results);
private:
void readSearchOutput();
void processFailed();
void processFinished(int exitcode);
bool parseSearchResult(const QString &line, SearchResult &searchResult);
const QString m_pattern;
const QString m_category;
const QStringList m_usedPlugins;
SearchPluginManager *m_manager;
QProcess *m_searchProcess;
QTimer *m_searchTimeout;
QByteArray m_searchResultLineTruncated;
bool m_searchCancelled = false;
QList<SearchResult> m_results;
};

282
src/base/searchengine.cpp → src/base/search/searchpluginmanager.cpp

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -27,33 +27,25 @@
* exception statement from your version. * exception statement from your version.
*/ */
#include "searchpluginmanager.h"
#include <QDebug>
#include <QDir>
#include <QDomDocument> #include <QDomDocument>
#include <QDomNode>
#include <QDomElement> #include <QDomElement>
#include <QDir> #include <QDomNode>
#include <QList>
#include <QProcess> #include <QProcess>
#include <QDebug>
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/net/downloadmanager.h" #include "base/net/downloadmanager.h"
#include "base/net/downloadhandler.h" #include "base/net/downloadhandler.h"
#include "searchengine.h" #include "base/preferences.h"
#include "base/profile.h"
enum SearchResultColumn #include "base/utils/fs.h"
{ #include "base/utils/misc.h"
PL_DL_LINK, #include "searchdownloadhandler.h"
PL_NAME, #include "searchhandler.h"
PL_SIZE,
PL_SEEDS,
PL_LEECHS,
PL_ENGINE_URL,
PL_DESC_LINK,
NB_PLUGIN_COLUMNS
};
static inline void removePythonScriptIfExists(const QString &scriptPath) static inline void removePythonScriptIfExists(const QString &scriptPath)
{ {
@ -61,39 +53,36 @@ static inline void removePythonScriptIfExists(const QString &scriptPath)
Utils::Fs::forceRemove(scriptPath + "c"); Utils::Fs::forceRemove(scriptPath + "c");
} }
const QHash<QString, QString> SearchEngine::m_categoryNames = SearchEngine::initializeCategoryNames(); const QHash<QString, QString> SearchPluginManager::m_categoryNames {
{"all", QT_TRANSLATE_NOOP("SearchEngine", "All categories")},
{"movies", QT_TRANSLATE_NOOP("SearchEngine", "Movies")},
{"tv", QT_TRANSLATE_NOOP("SearchEngine", "TV shows")},
{"music", QT_TRANSLATE_NOOP("SearchEngine", "Music")},
{"games", QT_TRANSLATE_NOOP("SearchEngine", "Games")},
{"anime", QT_TRANSLATE_NOOP("SearchEngine", "Anime")},
{"software", QT_TRANSLATE_NOOP("SearchEngine", "Software")},
{"pictures", QT_TRANSLATE_NOOP("SearchEngine", "Pictures")},
{"books", QT_TRANSLATE_NOOP("SearchEngine", "Books")}
};
SearchEngine::SearchEngine() SearchPluginManager::SearchPluginManager()
: m_updateUrl(QString("http://searchplugins.qbittorrent.org/%1/engines/").arg(Utils::Misc::pythonVersion() >= 3 ? "nova3" : "nova")) : m_updateUrl(QString("http://searchplugins.qbittorrent.org/%1/engines/").arg(Utils::Misc::pythonVersion() >= 3 ? "nova3" : "nova"))
, m_searchStopped(false)
{ {
updateNova(); updateNova();
m_searchProcess = new QProcess(this);
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_searchProcess, &QProcess::started, this, &SearchEngine::searchStarted);
connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchEngine::readSearchOutput);
connect(m_searchProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished), this, &SearchEngine::processFinished);
m_searchTimeout = new QTimer(this);
m_searchTimeout->setSingleShot(true);
connect(m_searchTimeout, &QTimer::timeout, this, &SearchEngine::onTimeout);
update(); update();
} }
SearchEngine::~SearchEngine() SearchPluginManager::~SearchPluginManager()
{ {
qDeleteAll(m_plugins); qDeleteAll(m_plugins);
cancelSearch();
} }
QStringList SearchEngine::allPlugins() const QStringList SearchPluginManager::allPlugins() const
{ {
return m_plugins.keys(); return m_plugins.keys();
} }
QStringList SearchEngine::enabledPlugins() const QStringList SearchPluginManager::enabledPlugins() const
{ {
QStringList plugins; QStringList plugins;
foreach (const PluginInfo *plugin, m_plugins.values()) { foreach (const PluginInfo *plugin, m_plugins.values()) {
@ -104,7 +93,7 @@ QStringList SearchEngine::enabledPlugins() const
return plugins; return plugins;
} }
QStringList SearchEngine::supportedCategories() const QStringList SearchPluginManager::supportedCategories() const
{ {
QStringList result; QStringList result;
foreach (const PluginInfo *plugin, m_plugins.values()) { foreach (const PluginInfo *plugin, m_plugins.values()) {
@ -119,17 +108,12 @@ QStringList SearchEngine::supportedCategories() const
return result; return result;
} }
PluginInfo *SearchEngine::pluginInfo(const QString &name) const PluginInfo *SearchPluginManager::pluginInfo(const QString &name) const
{
return m_plugins.value(name, 0);
}
bool SearchEngine::isActive() const
{ {
return (m_searchProcess->state() != QProcess::NotRunning); return m_plugins.value(name);
} }
void SearchEngine::enablePlugin(const QString &name, bool enabled) void SearchPluginManager::enablePlugin(const QString &name, bool enabled)
{ {
PluginInfo *plugin = m_plugins.value(name, 0); PluginInfo *plugin = m_plugins.value(name, 0);
if (plugin) { if (plugin) {
@ -148,13 +132,13 @@ void SearchEngine::enablePlugin(const QString &name, bool enabled)
} }
// Updates shipped plugin // Updates shipped plugin
void SearchEngine::updatePlugin(const QString &name) void SearchPluginManager::updatePlugin(const QString &name)
{ {
installPlugin(QString("%1%2.py").arg(m_updateUrl).arg(name)); installPlugin(QString("%1%2.py").arg(m_updateUrl).arg(name));
} }
// Install or update plugin from file or url // Install or update plugin from file or url
void SearchEngine::installPlugin(const QString &source) void SearchPluginManager::installPlugin(const QString &source)
{ {
qDebug("Asked to install plugin at %s", qUtf8Printable(source)); qDebug("Asked to install plugin at %s", qUtf8Printable(source));
@ -162,8 +146,8 @@ void SearchEngine::installPlugin(const QString &source)
using namespace Net; using namespace Net;
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(source, true); DownloadHandler *handler = DownloadManager::instance()->downloadUrl(source, true);
connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished) connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished)
, this, &SearchEngine::pluginDownloaded); , this, &SearchPluginManager::pluginDownloaded);
connect(handler, &DownloadHandler::downloadFailed, this, &SearchEngine::pluginDownloadFailed); connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::pluginDownloadFailed);
} }
else { else {
QString path = source; QString path = source;
@ -180,7 +164,7 @@ void SearchEngine::installPlugin(const QString &source)
} }
} }
void SearchEngine::installPlugin_impl(const QString &name, const QString &path) void SearchPluginManager::installPlugin_impl(const QString &name, const QString &path)
{ {
PluginVersion newVersion = getPluginVersion(path); PluginVersion newVersion = getPluginVersion(path);
qDebug() << "Version to be installed:" << newVersion; qDebug() << "Version to be installed:" << newVersion;
@ -229,7 +213,7 @@ void SearchEngine::installPlugin_impl(const QString &name, const QString &path)
} }
} }
bool SearchEngine::uninstallPlugin(const QString &name) bool SearchPluginManager::uninstallPlugin(const QString &name)
{ {
// remove it from hard drive // remove it from hard drive
QDir pluginsFolder(pluginsLocation()); QDir pluginsFolder(pluginsLocation());
@ -246,7 +230,7 @@ bool SearchEngine::uninstallPlugin(const QString &name)
return true; return true;
} }
void SearchEngine::updateIconPath(PluginInfo * const plugin) void SearchPluginManager::updateIconPath(PluginInfo * const plugin)
{ {
if (!plugin) return; if (!plugin) return;
QString iconPath = QString("%1/%2.png").arg(pluginsLocation()).arg(plugin->name); QString iconPath = QString("%1/%2.png").arg(pluginsLocation()).arg(plugin->name);
@ -260,86 +244,45 @@ void SearchEngine::updateIconPath(PluginInfo * const plugin)
} }
} }
void SearchEngine::checkForUpdates() void SearchPluginManager::checkForUpdates()
{ {
// Download version file from update server // Download version file from update server
using namespace Net; using namespace Net;
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(m_updateUrl + "versions.txt"); DownloadHandler *handler = DownloadManager::instance()->downloadUrl(m_updateUrl + "versions.txt");
connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QByteArray &)>(&DownloadHandler::downloadFinished) connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QByteArray &)>(&DownloadHandler::downloadFinished)
, this, &SearchEngine::versionInfoDownloaded); , this, &SearchPluginManager::versionInfoDownloaded);
connect(handler, &DownloadHandler::downloadFailed, this, &SearchEngine::versionInfoDownloadFailed); connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::versionInfoDownloadFailed);
} }
void SearchEngine::cancelSearch() SearchDownloadHandler *SearchPluginManager::downloadTorrent(const QString &siteUrl, const QString &url)
{ {
if (m_searchProcess->state() != QProcess::NotRunning) { return new SearchDownloadHandler {siteUrl, url, this};
#ifdef Q_OS_WIN
m_searchProcess->kill();
#else
m_searchProcess->terminate();
#endif
m_searchStopped = true;
m_searchTimeout->stop();
m_searchProcess->waitForFinished(1000);
}
}
void SearchEngine::downloadTorrent(const QString &siteUrl, const QString &url)
{
QProcess *downloadProcess = new QProcess(this);
downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(downloadProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished), this, &SearchEngine::torrentFileDownloadFinished);
m_downloaders << downloadProcess;
QStringList params {
Utils::Fs::toNativePath(engineLocation() + "/nova2dl.py"),
siteUrl,
url
};
// Launch search
downloadProcess->start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
} }
void SearchEngine::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins) SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins)
{ {
// Search process already running or
// No search pattern entered // No search pattern entered
if ((m_searchProcess->state() != QProcess::NotRunning) || pattern.isEmpty()) { Q_ASSERT(!pattern.isEmpty());
emit searchFailed();
return;
}
// Reload environment variables (proxy)
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
QStringList params; return new SearchHandler {pattern, category, usedPlugins, this};
m_searchStopped = false;
params << Utils::Fs::toNativePath(engineLocation() + "/nova2.py");
params << usedPlugins.join(",");
params << category;
params << pattern.split(" ");
// Launch search
m_searchProcess->start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
m_searchTimeout->start(180000); // 3min
} }
QString SearchEngine::categoryFullName(const QString &categoryName) QString SearchPluginManager::categoryFullName(const QString &categoryName)
{ {
return tr(m_categoryNames.value(categoryName).toUtf8().constData()); return tr(m_categoryNames.value(categoryName).toUtf8().constData());
} }
QString SearchEngine::pluginFullName(const QString &pluginName) QString SearchPluginManager::pluginFullName(const QString &pluginName)
{ {
return pluginInfo(pluginName) ? pluginInfo(pluginName)->fullName : QString(); return pluginInfo(pluginName) ? pluginInfo(pluginName)->fullName : QString();
} }
QString SearchEngine::pluginsLocation() QString SearchPluginManager::pluginsLocation()
{ {
return QString("%1/engines").arg(engineLocation()); return QString("%1/engines").arg(engineLocation());
} }
QString SearchEngine::engineLocation() QString SearchPluginManager::engineLocation()
{ {
QString folder = "nova"; QString folder = "nova";
if (Utils::Misc::pythonVersion() >= 3) if (Utils::Misc::pythonVersion() >= 3)
@ -351,32 +294,19 @@ QString SearchEngine::engineLocation()
return location; return location;
} }
// Slot called when QProcess is Finished void SearchPluginManager::versionInfoDownloaded(const QString &url, const QByteArray &data)
// QProcess can be finished for 3 reasons :
// Error | Stopped by user | Finished normally
void SearchEngine::processFinished(int exitcode)
{
m_searchTimeout->stop();
if (exitcode == 0)
emit searchFinished(m_searchStopped);
else
emit searchFailed();
}
void SearchEngine::versionInfoDownloaded(const QString &url, const QByteArray &data)
{ {
Q_UNUSED(url) Q_UNUSED(url)
parseVersionInfo(data); parseVersionInfo(data);
} }
void SearchEngine::versionInfoDownloadFailed(const QString &url, const QString &reason) void SearchPluginManager::versionInfoDownloadFailed(const QString &url, const QString &reason)
{ {
Q_UNUSED(url) Q_UNUSED(url)
emit checkForUpdatesFailed(tr("Update server is temporarily unavailable. %1").arg(reason)); emit checkForUpdatesFailed(tr("Update server is temporarily unavailable. %1").arg(reason));
} }
void SearchEngine::pluginDownloaded(const QString &url, QString filePath) void SearchPluginManager::pluginDownloaded(const QString &url, QString filePath)
{ {
filePath = Utils::Fs::fromNativePath(filePath); filePath = Utils::Fs::fromNativePath(filePath);
@ -386,7 +316,7 @@ void SearchEngine::pluginDownloaded(const QString &url, QString filePath)
Utils::Fs::forceRemove(filePath); Utils::Fs::forceRemove(filePath);
} }
void SearchEngine::pluginDownloadFailed(const QString &url, const QString &reason) void SearchPluginManager::pluginDownloadFailed(const QString &url, const QString &reason)
{ {
QString pluginName = url.split('/').last(); QString pluginName = url.split('/').last();
pluginName.replace(".py", "", Qt::CaseInsensitive); pluginName.replace(".py", "", Qt::CaseInsensitive);
@ -396,23 +326,8 @@ void SearchEngine::pluginDownloadFailed(const QString &url, const QString &reaso
emit pluginInstallationFailed(pluginName, tr("Failed to download the plugin file. %1").arg(reason)); emit pluginInstallationFailed(pluginName, tr("Failed to download the plugin file. %1").arg(reason));
} }
void SearchEngine::torrentFileDownloadFinished(int exitcode)
{
QProcess *downloadProcess = static_cast<QProcess*>(sender());
if (exitcode == 0) {
QString line = QString::fromUtf8(downloadProcess->readAllStandardOutput()).trimmed();
QStringList parts = line.split(' ');
if (parts.size() == 2)
emit torrentFileDownloaded(parts[0]);
}
qDebug() << "Deleting downloadProcess";
m_downloaders.removeOne(downloadProcess);
downloadProcess->deleteLater();
}
// Update nova.py search plugin if necessary // Update nova.py search plugin if necessary
void SearchEngine::updateNova() void SearchPluginManager::updateNova()
{ {
qDebug("Updating nova"); qDebug("Updating nova");
@ -477,35 +392,7 @@ void SearchEngine::updateNova()
Utils::Fs::removeDirRecursive(destDir.absoluteFilePath("__pycache__")); Utils::Fs::removeDirRecursive(destDir.absoluteFilePath("__pycache__"));
} }
void SearchEngine::onTimeout() void SearchPluginManager::update()
{
cancelSearch();
}
// search QProcess return output as soon as it gets new
// stuff to read. We split it into lines and parse each
// line to SearchResult calling parseSearchResult().
void SearchEngine::readSearchOutput()
{
QByteArray output = m_searchProcess->readAllStandardOutput();
output.replace("\r", "");
QList<QByteArray> lines = output.split('\n');
if (!m_searchResultLineTruncated.isEmpty())
lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
m_searchResultLineTruncated = lines.takeLast().trimmed();
QList<SearchResult> searchResultList;
foreach (const QByteArray &line, lines) {
SearchResult searchResult;
if (parseSearchResult(QString::fromUtf8(line), searchResult))
searchResultList << searchResult;
}
if (!searchResultList.isEmpty())
emit newSearchResults(searchResultList);
}
void SearchEngine::update()
{ {
QProcess nova; QProcess nova;
nova.setEnvironment(QProcess::systemEnvironment()); nova.setEnvironment(QProcess::systemEnvironment());
@ -565,34 +452,7 @@ void SearchEngine::update()
} }
} }
// Parse one line of search results list void SearchPluginManager::parseVersionInfo(const QByteArray &info)
// Line is in the following form:
// file url | file name | file size | nb seeds | nb leechers | Search engine url
bool SearchEngine::parseSearchResult(const QString &line, SearchResult &searchResult)
{
const QStringList parts = line.split("|");
const int nbFields = parts.size();
if (nbFields < (NB_PLUGIN_COLUMNS - 1)) return false; // -1 because desc_link is optional
searchResult = SearchResult();
searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed(); // download URL
searchResult.fileName = parts.at(PL_NAME).trimmed(); // Name
searchResult.fileSize = parts.at(PL_SIZE).trimmed().toLongLong(); // Size
bool ok = false;
searchResult.nbSeeders = parts.at(PL_SEEDS).trimmed().toLongLong(&ok); // Seeders
if (!ok || (searchResult.nbSeeders < 0))
searchResult.nbSeeders = -1;
searchResult.nbLeechers = parts.at(PL_LEECHS).trimmed().toLongLong(&ok); // Leechers
if (!ok || (searchResult.nbLeechers < 0))
searchResult.nbLeechers = -1;
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed(); // Search site URL
if (nbFields == NB_PLUGIN_COLUMNS)
searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed(); // Description Link
return true;
}
void SearchEngine::parseVersionInfo(const QByteArray &info)
{ {
qDebug("Checking if update is needed"); qDebug("Checking if update is needed");
@ -627,7 +487,7 @@ void SearchEngine::parseVersionInfo(const QByteArray &info)
emit checkForUpdatesFinished(updateInfo); emit checkForUpdatesFinished(updateInfo);
} }
bool SearchEngine::isUpdateNeeded(QString pluginName, PluginVersion newVersion) const bool SearchPluginManager::isUpdateNeeded(QString pluginName, PluginVersion newVersion) const
{ {
PluginInfo *plugin = pluginInfo(pluginName); PluginInfo *plugin = pluginInfo(pluginName);
if (!plugin) return true; if (!plugin) return true;
@ -637,29 +497,12 @@ bool SearchEngine::isUpdateNeeded(QString pluginName, PluginVersion newVersion)
return (newVersion > oldVersion); return (newVersion > oldVersion);
} }
QString SearchEngine::pluginPath(const QString &name) QString SearchPluginManager::pluginPath(const QString &name)
{ {
return QString("%1/%2.py").arg(pluginsLocation()).arg(name); return QString("%1/%2.py").arg(pluginsLocation()).arg(name);
} }
QHash<QString, QString> SearchEngine::initializeCategoryNames() PluginVersion SearchPluginManager::getPluginVersion(QString filePath)
{
QHash<QString, QString> result;
result["all"] = QT_TRANSLATE_NOOP("SearchEngine", "All categories");
result["movies"] = QT_TRANSLATE_NOOP("SearchEngine", "Movies");
result["tv"] = QT_TRANSLATE_NOOP("SearchEngine", "TV shows");
result["music"] = QT_TRANSLATE_NOOP("SearchEngine", "Music");
result["games"] = QT_TRANSLATE_NOOP("SearchEngine", "Games");
result["anime"] = QT_TRANSLATE_NOOP("SearchEngine", "Anime");
result["software"] = QT_TRANSLATE_NOOP("SearchEngine", "Software");
result["pictures"] = QT_TRANSLATE_NOOP("SearchEngine", "Pictures");
result["books"] = QT_TRANSLATE_NOOP("SearchEngine", "Books");
return result;
}
PluginVersion SearchEngine::getPluginVersion(QString filePath)
{ {
QFile plugin(filePath); QFile plugin(filePath);
if (!plugin.exists()) { if (!plugin.exists()) {
@ -682,8 +525,9 @@ PluginVersion SearchEngine::getPluginVersion(QString filePath)
LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')") LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')")
.arg(Utils::Fs::fileName(filePath)).arg(QString::fromUtf8(line)), Log::MsgType::WARNING); .arg(Utils::Fs::fileName(filePath)).arg(QString::fromUtf8(line)), Log::MsgType::WARNING);
} }
else else {
qDebug() << "plugin" << filePath << "version: " << version; qDebug() << "plugin" << filePath << "version: " << version;
}
break; break;
} }
} }

69
src/base/searchengine.h → src/base/search/searchpluginmanager.h

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -27,19 +27,14 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifndef SEARCHENGINE_H #pragma once
#define SEARCHENGINE_H
#include <QHash> #include <QHash>
#include <QList>
#include <QMetaType> #include <QMetaType>
#include <QObject> #include <QObject>
#include "base/utils/version.h" #include "base/utils/version.h"
class QProcess;
class QTimer;
using PluginVersion = Utils::Version<unsigned short, 2>; using PluginVersion = Utils::Version<unsigned short, 2>;
Q_DECLARE_METATYPE(PluginVersion) Q_DECLARE_METATYPE(PluginVersion)
@ -54,32 +49,23 @@ struct PluginInfo
bool enabled; bool enabled;
}; };
struct SearchResult class SearchDownloadHandler;
{ class SearchHandler;
QString fileName;
QString fileUrl;
qlonglong fileSize;
qlonglong nbSeeders;
qlonglong nbLeechers;
QString siteUrl;
QString descrLink;
};
class SearchEngine: public QObject class SearchPluginManager : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(SearchPluginManager)
public: public:
SearchEngine(); SearchPluginManager();
~SearchEngine(); ~SearchPluginManager() override;
QStringList allPlugins() const; QStringList allPlugins() const;
QStringList enabledPlugins() const; QStringList enabledPlugins() const;
QStringList supportedCategories() const; QStringList supportedCategories() const;
PluginInfo *pluginInfo(const QString &name) const; PluginInfo *pluginInfo(const QString &name) const;
bool isActive() const;
void enablePlugin(const QString &name, bool enabled = true); void enablePlugin(const QString &name, bool enabled = true);
void updatePlugin(const QString &name); void updatePlugin(const QString &name);
void installPlugin(const QString &source); void installPlugin(const QString &source);
@ -87,22 +73,16 @@ public:
static void updateIconPath(PluginInfo * const plugin); static void updateIconPath(PluginInfo * const plugin);
void checkForUpdates(); void checkForUpdates();
void startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins); SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
void cancelSearch(); SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url);
void downloadTorrent(const QString &siteUrl, const QString &url);
static PluginVersion getPluginVersion(QString filePath); static PluginVersion getPluginVersion(QString filePath);
static QString categoryFullName(const QString &categoryName); static QString categoryFullName(const QString &categoryName);
QString pluginFullName(const QString &pluginName); QString pluginFullName(const QString &pluginName);
static QString pluginsLocation(); static QString pluginsLocation();
static QString engineLocation();
signals: signals:
void searchStarted();
void searchFinished(bool cancelled);
void searchFailed();
void newSearchResults(const QList<SearchResult> &results);
void pluginEnabled(const QString &name, bool enabled); void pluginEnabled(const QString &name, bool enabled);
void pluginInstalled(const QString &name); void pluginInstalled(const QString &name);
void pluginInstallationFailed(const QString &name, const QString &reason); void pluginInstallationFailed(const QString &name, const QString &reason);
@ -113,40 +93,23 @@ signals:
void checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo); void checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo);
void checkForUpdatesFailed(const QString &reason); void checkForUpdatesFailed(const QString &reason);
void torrentFileDownloaded(const QString &path);
private slots:
void onTimeout();
void readSearchOutput();
void processFinished(int exitcode);
void versionInfoDownloaded(const QString &url, const QByteArray &data);
void versionInfoDownloadFailed(const QString &url, const QString &reason);
void pluginDownloaded(const QString &url, QString filePath);
void pluginDownloadFailed(const QString &url, const QString &reason);
void torrentFileDownloadFinished(int exitcode);
private: private:
void update(); void update();
void updateNova(); void updateNova();
bool parseSearchResult(const QString &line, SearchResult &searchResult);
void parseVersionInfo(const QByteArray &info); void parseVersionInfo(const QByteArray &info);
void installPlugin_impl(const QString &name, const QString &path); void installPlugin_impl(const QString &name, const QString &path);
bool isUpdateNeeded(QString pluginName, PluginVersion newVersion) const; bool isUpdateNeeded(QString pluginName, PluginVersion newVersion) const;
static QString engineLocation(); void versionInfoDownloaded(const QString &url, const QByteArray &data);
void versionInfoDownloadFailed(const QString &url, const QString &reason);
void pluginDownloaded(const QString &url, QString filePath);
void pluginDownloadFailed(const QString &url, const QString &reason);
static QString pluginPath(const QString &name); static QString pluginPath(const QString &name);
static QHash<QString, QString> initializeCategoryNames();
static const QHash<QString, QString> m_categoryNames; static const QHash<QString, QString> m_categoryNames;
const QString m_updateUrl; const QString m_updateUrl;
QHash<QString, PluginInfo*> m_plugins; QHash<QString, PluginInfo*> m_plugins;
QProcess *m_searchProcess;
bool m_searchStopped;
QTimer *m_searchTimeout;
QByteArray m_searchResultLineTruncated;
QList<QProcess*> m_downloaders;
}; };
#endif // SEARCHENGINE_H

16
src/gui/search/pluginselectdlg.cpp

@ -61,7 +61,7 @@ enum PluginColumns
PLUGIN_ID PLUGIN_ID
}; };
PluginSelectDlg::PluginSelectDlg(SearchEngine *pluginManager, QWidget *parent) PluginSelectDlg::PluginSelectDlg(SearchPluginManager *pluginManager, QWidget *parent)
: QDialog(parent) : QDialog(parent)
, m_ui(new Ui::PluginSelectDlg()) , m_ui(new Ui::PluginSelectDlg())
, m_pluginManager(pluginManager) , m_pluginManager(pluginManager)
@ -90,12 +90,12 @@ PluginSelectDlg::PluginSelectDlg(SearchEngine *pluginManager, QWidget *parent)
loadSupportedSearchPlugins(); loadSupportedSearchPlugins();
connect(m_pluginManager, &SearchEngine::pluginInstalled, this, &PluginSelectDlg::pluginInstalled); connect(m_pluginManager, &SearchPluginManager::pluginInstalled, this, &PluginSelectDlg::pluginInstalled);
connect(m_pluginManager, &SearchEngine::pluginInstallationFailed, this, &PluginSelectDlg::pluginInstallationFailed); connect(m_pluginManager, &SearchPluginManager::pluginInstallationFailed, this, &PluginSelectDlg::pluginInstallationFailed);
connect(m_pluginManager, &SearchEngine::pluginUpdated, this, &PluginSelectDlg::pluginUpdated); connect(m_pluginManager, &SearchPluginManager::pluginUpdated, this, &PluginSelectDlg::pluginUpdated);
connect(m_pluginManager, &SearchEngine::pluginUpdateFailed, this, &PluginSelectDlg::pluginUpdateFailed); connect(m_pluginManager, &SearchPluginManager::pluginUpdateFailed, this, &PluginSelectDlg::pluginUpdateFailed);
connect(m_pluginManager, &SearchEngine::checkForUpdatesFinished, this, &PluginSelectDlg::checkForUpdatesFinished); connect(m_pluginManager, &SearchPluginManager::checkForUpdatesFinished, this, &PluginSelectDlg::checkForUpdatesFinished);
connect(m_pluginManager, &SearchEngine::checkForUpdatesFailed, this, &PluginSelectDlg::checkForUpdatesFailed); connect(m_pluginManager, &SearchPluginManager::checkForUpdatesFailed, this, &PluginSelectDlg::checkForUpdatesFailed);
Utils::Gui::resize(this); Utils::Gui::resize(this);
show(); show();
@ -387,7 +387,7 @@ void PluginSelectDlg::iconDownloaded(const QString &url, QString filePath)
if (!plugin) continue; if (!plugin) continue;
QString iconPath = QString("%1/%2.%3") QString iconPath = QString("%1/%2.%3")
.arg(SearchEngine::pluginsLocation()) .arg(SearchPluginManager::pluginsLocation())
.arg(id) .arg(id)
.arg(url.endsWith(".ico", Qt::CaseInsensitive) ? "ico" : "png"); .arg(url.endsWith(".ico", Qt::CaseInsensitive) ? "ico" : "png");
if (QFile::copy(filePath, iconPath)) { if (QFile::copy(filePath, iconPath)) {

6
src/gui/search/pluginselectdlg.h

@ -34,7 +34,7 @@
#include <QDialog> #include <QDialog>
#include "base/searchengine.h" #include "base/search/searchpluginmanager.h"
class QDropEvent; class QDropEvent;
class QStringList; class QStringList;
@ -50,7 +50,7 @@ class PluginSelectDlg: public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit PluginSelectDlg(SearchEngine *pluginManager, QWidget *parent = 0); explicit PluginSelectDlg(SearchPluginManager *pluginManager, QWidget *parent = 0);
~PluginSelectDlg(); ~PluginSelectDlg();
QList<QTreeWidgetItem*> findItemsWithUrl(QString url); QList<QTreeWidgetItem*> findItemsWithUrl(QString url);
@ -89,7 +89,7 @@ private:
void finishPluginUpdate(); void finishPluginUpdate();
Ui::PluginSelectDlg *m_ui; Ui::PluginSelectDlg *m_ui;
SearchEngine *m_pluginManager; SearchPluginManager *m_pluginManager;
QStringList m_updatedPlugins; QStringList m_updatedPlugins;
int m_asyncOps; int m_asyncOps;
int m_pendingUpdates; int m_pendingUpdates;

255
src/gui/search/searchtab.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez * Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -24,37 +25,41 @@
* modify file(s), you may extend this exception to your version of the file(s), * 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 * but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version. * exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/ */
#include "searchtab.h"
#include <QApplication> #include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QDir> #include <QDir>
#include <QMenu>
#include <QTreeView>
#include <QStandardItemModel>
#include <QHeaderView> #include <QHeaderView>
#include <QSortFilterProxyModel>
#include <QLabel> #include <QLabel>
#include <QMenu>
#include <QPalette> #include <QPalette>
#include <QVBoxLayout> #include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QTableView> #include <QTableView>
#include <QTreeView>
#include <QVBoxLayout>
#include "base/utils/misc.h" #include "base/bittorrent/session.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/search/searchdownloadhandler.h"
#include "base/search/searchhandler.h"
#include "base/search/searchpluginmanager.h"
#include "base/settingvalue.h" #include "base/settingvalue.h"
#include "base/utils/misc.h"
#include "addnewtorrentdialog.h"
#include "guiiconprovider.h" #include "guiiconprovider.h"
#include "searchsortmodel.h"
#include "searchlistdelegate.h" #include "searchlistdelegate.h"
#include "searchwidget.h" #include "searchsortmodel.h"
#include "searchtab.h"
#include "ui_searchtab.h" #include "ui_searchtab.h"
SearchTab::SearchTab(SearchWidget *parent) SearchTab::SearchTab(SearchHandler *searchHandler, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, m_ui(new Ui::SearchTab()) , m_ui(new Ui::SearchTab)
, m_parent(parent) , m_searchHandler(searchHandler)
, m_status(Status::NoResults)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@ -84,6 +89,7 @@ SearchTab::SearchTab(SearchWidget *parent)
m_proxyModel = new SearchSortModel(this); m_proxyModel = new SearchSortModel(this);
m_proxyModel->setDynamicSortFilter(true); m_proxyModel->setDynamicSortFilter(true);
m_proxyModel->setSourceModel(m_searchListModel); m_proxyModel->setSourceModel(m_searchListModel);
m_proxyModel->setNameFilter(searchHandler->pattern());
m_ui->resultsBrowser->setModel(m_proxyModel); m_ui->resultsBrowser->setModel(m_proxyModel);
m_searchDelegate = new SearchListDelegate(this); m_searchDelegate = new SearchListDelegate(this);
@ -113,30 +119,41 @@ SearchTab::SearchTab(SearchWidget *parent)
if ((m_ui->resultsBrowser->columnWidth(i) <= 0) && !m_ui->resultsBrowser->isColumnHidden(i)) if ((m_ui->resultsBrowser->columnWidth(i) <= 0) && !m_ui->resultsBrowser->isColumnHidden(i))
m_ui->resultsBrowser->resizeColumnToContents(i); m_ui->resultsBrowser->resizeColumnToContents(i);
// Connect signals to slots (search part)
connect(m_ui->resultsBrowser, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(downloadItem(const QModelIndex&)));
header()->setContextMenuPolicy(Qt::CustomContextMenu); header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(header(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(displayToggleColumnsMenu(const QPoint &))); connect(header(), &QWidget::customContextMenuRequested, this, &SearchTab::displayToggleColumnsMenu);
connect(header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(saveSettings())); connect(header(), &QHeaderView::sectionResized, this, &SearchTab::saveSettings);
connect(header(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(saveSettings())); connect(header(), &QHeaderView::sectionMoved, this, &SearchTab::saveSettings);
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(saveSettings())); connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchTab::saveSettings);
fillFilterComboBoxes(); fillFilterComboBoxes();
updateFilter(); updateFilter();
connect(m_ui->filterMode, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); connect(m_ui->filterMode, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged)
connect(m_ui->minSeeds, SIGNAL(editingFinished()), this, SLOT(updateFilter())); , this, &SearchTab::updateFilter);
connect(m_ui->minSeeds, SIGNAL(valueChanged(int)), this, SLOT(updateFilter())); connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchTab::updateFilter);
connect(m_ui->maxSeeds, SIGNAL(editingFinished()), this, SLOT(updateFilter())); connect(m_ui->minSeeds, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged)
connect(m_ui->maxSeeds, SIGNAL(valueChanged(int)), this, SLOT(updateFilter())); , this, &SearchTab::updateFilter);
connect(m_ui->minSize, SIGNAL(editingFinished()), this, SLOT(updateFilter())); connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchTab::updateFilter);
connect(m_ui->minSize, SIGNAL(valueChanged(double)), this, SLOT(updateFilter())); connect(m_ui->maxSeeds, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged)
connect(m_ui->maxSize, SIGNAL(editingFinished()), this, SLOT(updateFilter())); , this, &SearchTab::updateFilter);
connect(m_ui->maxSize, SIGNAL(valueChanged(double)), this, SLOT(updateFilter())); connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchTab::updateFilter);
connect(m_ui->minSizeUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); connect(m_ui->minSize, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged)
connect(m_ui->maxSizeUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); , this, &SearchTab::updateFilter);
connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchTab::updateFilter);
connect(m_ui->maxSize, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged)
, this, &SearchTab::updateFilter);
connect(m_ui->minSizeUnit, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged)
, this, &SearchTab::updateFilter);
connect(m_ui->maxSizeUnit, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged)
, this, &SearchTab::updateFilter);
connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchTab::onItemDoubleClicked);
connect(searchHandler, &SearchHandler::newSearchResults, this, &SearchTab::appendSearchResults);
connect(searchHandler, &SearchHandler::searchFinished, this, &SearchTab::searchFinished);
connect(searchHandler, &SearchHandler::searchFailed, this, &SearchTab::searchFailed);
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
} }
SearchTab::~SearchTab() SearchTab::~SearchTab()
@ -145,80 +162,138 @@ SearchTab::~SearchTab()
delete m_ui; delete m_ui;
} }
void SearchTab::downloadItem(const QModelIndex &index) void SearchTab::onItemDoubleClicked(const QModelIndex &index)
{ {
QString torrentUrl = m_proxyModel->data(m_proxyModel->index(index.row(), SearchSortModel::DL_LINK)).toString();
QString siteUrl = m_proxyModel->data(m_proxyModel->index(index.row(), SearchSortModel::ENGINE_URL)).toString();
setRowColor(index.row(), QApplication::palette().color(QPalette::LinkVisited)); setRowColor(index.row(), QApplication::palette().color(QPalette::LinkVisited));
m_parent->downloadTorrent(siteUrl, torrentUrl); downloadTorrent(index);
} }
QHeaderView* SearchTab::header() const QHeaderView *SearchTab::header() const
{ {
return m_ui->resultsBrowser->header(); return m_ui->resultsBrowser->header();
} }
QTreeView* SearchTab::getCurrentTreeView() const // Set the color of a row in data model
void SearchTab::setRowColor(int row, const QColor &color)
{ {
return m_ui->resultsBrowser; m_proxyModel->setDynamicSortFilter(false);
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
m_proxyModel->setDynamicSortFilter(true);
} }
SearchSortModel* SearchTab::getCurrentSearchListProxy() const SearchTab::Status SearchTab::status() const
{ {
return m_proxyModel; return m_status;
} }
QStandardItemModel* SearchTab::getCurrentSearchListModel() const int SearchTab::visibleResultsCount() const
{ {
return m_searchListModel; return m_proxyModel->rowCount();
} }
// Set the color of a row in data model void SearchTab::cancelSearch()
void SearchTab::setRowColor(int row, const QColor &color)
{ {
m_proxyModel->setDynamicSortFilter(false); m_searchHandler->cancelSearch();
for (int i = 0; i < m_proxyModel->columnCount(); ++i) }
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
m_proxyModel->setDynamicSortFilter(true); void SearchTab::downloadTorrents()
{
const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
for (const QModelIndex &rowIndex : rows)
downloadTorrent(rowIndex);
} }
SearchTab::Status SearchTab::status() const void SearchTab::openTorrentPages()
{ {
return m_status; const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
for (const QModelIndex &rowIndex : rows) {
const QString descrLink = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::DESC_LINK)).toString();
if (!descrLink.isEmpty())
QDesktopServices::openUrl(QUrl::fromEncoded(descrLink.toUtf8()));
}
}
void SearchTab::copyTorrentURLs()
{
const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
QStringList urls;
for (const QModelIndex &rowIndex : rows) {
const QString descrLink = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::DESC_LINK)).toString();
if (!descrLink.isEmpty())
urls << descrLink;
}
if (!urls.empty()) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(urls.join("\n"));
}
} }
void SearchTab::setStatus(Status value) void SearchTab::setStatus(Status value)
{ {
if (m_status == value) return;
m_status = value; m_status = value;
setStatusTip(statusText(value)); setStatusTip(statusText(value));
const int thisTabIndex = m_parent->searchTabs()->indexOf(this); emit statusChanged();
m_parent->searchTabs()->setTabToolTip(thisTabIndex, statusTip()); }
m_parent->searchTabs()->setTabIcon(thisTabIndex, GuiIconProvider::instance()->getIcon(statusIconName(value)));
void SearchTab::downloadTorrent(const QModelIndex &rowIndex)
{
const QString torrentUrl = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::DL_LINK)).toString();
const QString siteUrl = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::ENGINE_URL)).toString();
if (torrentUrl.startsWith("bc://bt/", Qt::CaseInsensitive) || torrentUrl.startsWith("magnet:", Qt::CaseInsensitive)) {
addTorrentToSession(torrentUrl);
}
else {
SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(siteUrl, torrentUrl);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, this, &SearchTab::addTorrentToSession);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
}
}
void SearchTab::addTorrentToSession(const QString &source)
{
if (source.isEmpty()) return;
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(source, this);
else
BitTorrent::Session::instance()->addTorrent(source);
} }
void SearchTab::updateResultsCount() void SearchTab::updateResultsCount()
{ {
const int totalResults = getCurrentSearchListModel() ? getCurrentSearchListModel()->rowCount(QModelIndex()) : 0; const int totalResults = m_searchListModel->rowCount();
const int filteredResults = getCurrentSearchListProxy() ? getCurrentSearchListProxy()->rowCount(QModelIndex()) : totalResults; const int filteredResults = m_proxyModel->rowCount();
m_ui->resultsLbl->setText(tr("Results (showing <i>%1</i> out of <i>%2</i>):", "i.e: Search results") m_ui->resultsLbl->setText(tr("Results (showing <i>%1</i> out of <i>%2</i>):", "i.e: Search results")
.arg(filteredResults).arg(totalResults)); .arg(filteredResults).arg(totalResults));
m_noSearchResults = (totalResults == 0);
emit resultsCountUpdated();
} }
void SearchTab::updateFilter() void SearchTab::updateFilter()
{ {
using Utils::Misc::SizeUnit; using Utils::Misc::SizeUnit;
SearchSortModel* filterModel = getCurrentSearchListProxy();
filterModel->enableNameFilter(filteringMode() == NameFilteringMode::OnlyNames); m_proxyModel->enableNameFilter(filteringMode() == NameFilteringMode::OnlyNames);
// we update size and seeds filter parameters in the model even if they are disabled // we update size and seeds filter parameters in the model even if they are disabled
filterModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value()); m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
filterModel->setSizeFilter( m_proxyModel->setSizeFilter(
sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())), sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())),
sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex()))); sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
nameFilteringModeSetting() = filteringMode(); nameFilteringModeSetting() = filteringMode();
filterModel->invalidate(); m_proxyModel->invalidate();
updateResultsCount(); updateResultsCount();
} }
@ -273,24 +348,6 @@ QString SearchTab::statusText(SearchTab::Status st)
} }
} }
QString SearchTab::statusIconName(SearchTab::Status st)
{
switch (st) {
case Status::Ongoing:
return QLatin1String("task-ongoing");
case Status::Finished:
return QLatin1String("task-complete");
case Status::Aborted:
return QLatin1String("task-reject");
case Status::Error:
return QLatin1String("task-attention");
case Status::NoResults:
return QLatin1String("task-attention");
default:
return QString();
}
}
SearchTab::NameFilteringMode SearchTab::filteringMode() const SearchTab::NameFilteringMode SearchTab::filteringMode() const
{ {
return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt()); return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
@ -341,6 +398,40 @@ void SearchTab::displayToggleColumnsMenu(const QPoint&)
} }
} }
void SearchTab::searchFinished(bool cancelled)
{
if (cancelled)
setStatus(Status::Aborted);
else if (m_noSearchResults)
setStatus(Status::NoResults);
else
setStatus(Status::Finished);
}
void SearchTab::searchFailed()
{
setStatus(Status::Error);
}
void SearchTab::appendSearchResults(const QList<SearchResult> &results)
{
for (const SearchResult &result : results) {
// Add item to search result list
int row = m_searchListModel->rowCount();
m_searchListModel->insertRow(row);
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::NAME), result.fileName); // Name
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::DL_LINK), result.fileUrl); // download URL
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::SIZE), result.fileSize); // Size
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::SEEDS), result.nbSeeders); // Seeders
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::LEECHES), result.nbLeechers); // Leechers
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::ENGINE_URL), result.siteUrl); // Search site URL
m_searchListModel->setData(m_searchListModel->index(row, SearchSortModel::DESC_LINK), result.descrLink); // Description Link
}
updateResultsCount();
}
CachedSettingValue<SearchTab::NameFilteringMode> &SearchTab::nameFilteringModeSetting() CachedSettingValue<SearchTab::NameFilteringMode> &SearchTab::nameFilteringModeSetting()
{ {
static CachedSettingValue<NameFilteringMode> setting("Search/FilteringMode", NameFilteringMode::OnlyNames); static CachedSettingValue<NameFilteringMode> setting("Search/FilteringMode", NameFilteringMode::OnlyNames);

70
src/gui/search/searchtab.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2006 Christophe Dumez * Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -24,12 +25,9 @@
* modify file(s), you may extend this exception to your version of the file(s), * 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 * but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version. * exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/ */
#ifndef SEARCHTAB_H #pragma once
#define SEARCHTAB_H
#include <QWidget> #include <QWidget>
@ -38,46 +36,36 @@
class QLabel; class QLabel;
class QModelIndex; class QModelIndex;
class QTreeView;
class QHeaderView; class QHeaderView;
class QStandardItem;
class QStandardItemModel; class QStandardItemModel;
class QVBoxLayout; class QVBoxLayout;
template <typename T> class CachedSettingValue; template <typename T> class CachedSettingValue;
class SearchHandler;
class SearchSortModel; class SearchSortModel;
class SearchListDelegate; class SearchListDelegate;
class SearchWidget; struct SearchResult;
namespace Ui namespace Ui
{ {
class SearchTab; class SearchTab;
} }
class SearchTab: public QWidget class SearchTab : public QWidget
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(SearchTab)
public: public:
enum class NameFilteringMode enum class NameFilteringMode
{ {
Everywhere, Everywhere,
OnlyNames OnlyNames
}; };
Q_ENUM(NameFilteringMode) Q_ENUM(NameFilteringMode)
explicit SearchTab(SearchWidget *parent);
~SearchTab();
QStandardItemModel* getCurrentSearchListModel() const;
SearchSortModel* getCurrentSearchListProxy() const;
QTreeView* getCurrentTreeView() const;
QHeaderView* header() const;
void setRowColor(int row, const QColor &color);
enum class Status enum class Status
{ {
Ongoing, Ongoing,
@ -87,36 +75,50 @@ public:
NoResults NoResults
}; };
void setStatus(Status value); explicit SearchTab(SearchHandler *searchHandler, QWidget *parent = nullptr);
~SearchTab() override;
Status status() const; Status status() const;
int visibleResultsCount() const;
void updateResultsCount(); void cancelSearch();
public slots: void downloadTorrents();
void downloadItem(const QModelIndex &index); void openTorrentPages();
void copyTorrentURLs();
private slots: signals:
void resultsCountUpdated();
void statusChanged();
private:
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;
void updateFilter(); void updateFilter();
void displayToggleColumnsMenu(const QPoint&); void displayToggleColumnsMenu(const QPoint&);
void onItemDoubleClicked(const QModelIndex &index);
private: void searchFinished(bool cancelled);
void searchFailed();
void appendSearchResults(const QList<SearchResult> &results);
void updateResultsCount();
void setStatus(Status value);
void downloadTorrent(const QModelIndex &rowIndex);
void addTorrentToSession(const QString &source);
void fillFilterComboBoxes(); void fillFilterComboBoxes();
NameFilteringMode filteringMode() const; NameFilteringMode filteringMode() const;
static QString statusText(Status st); QHeaderView *header() const;
static QString statusIconName(Status st); void setRowColor(int row, const QColor &color);
static QString statusText(Status st);
static CachedSettingValue<NameFilteringMode>& nameFilteringModeSetting(); static CachedSettingValue<NameFilteringMode>& nameFilteringModeSetting();
Ui::SearchTab *m_ui; Ui::SearchTab *m_ui;
SearchHandler *m_searchHandler;
QStandardItemModel *m_searchListModel; QStandardItemModel *m_searchListModel;
SearchSortModel *m_proxyModel; SearchSortModel *m_proxyModel;
SearchListDelegate *m_searchDelegate; SearchListDelegate *m_searchDelegate;
SearchWidget *m_parent; Status m_status = Status::Ongoing;
Status m_status; bool m_noSearchResults = true;
}; };
Q_DECLARE_METATYPE(SearchTab::NameFilteringMode) Q_DECLARE_METATYPE(SearchTab::NameFilteringMode)
#endif // SEARCHTAB_H

321
src/gui/search/searchwidget.cpp

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -25,59 +25,76 @@
* modify file(s), you may extend this exception to your version of the file(s), * 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 * but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version. * exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/ */
#include "searchwidget.h" #include "searchwidget.h"
#include <QHeaderView> #include <iostream>
#include <QMessageBox> #ifdef Q_OS_WIN
#include <QSystemTrayIcon> #include <cstdlib>
#include <QTimer> #endif
#include <QDebug>
#include <QDir> #include <QDir>
#include <QFileDialog>
#include <QHeaderView>
#include <QMenu> #include <QMenu>
#include <QClipboard> #include <QMessageBox>
#include <QMimeData> #include <QMimeData>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QFileDialog>
#include <QDesktopServices>
#include <QClipboard>
#include <QProcess> #include <QProcess>
#include <QDebug> #include <QSignalMapper>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QSystemTrayIcon>
#include <QTextStream> #include <QTextStream>
#include <QTimer>
#include <QTreeView> #include <QTreeView>
#include <iostream>
#ifdef Q_OS_WIN
#include <stdlib.h>
#endif
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/preferences.h"
#include "base/search/searchpluginmanager.h"
#include "base/search/searchhandler.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/preferences.h"
#include "base/searchengine.h"
#include "searchlistdelegate.h"
#include "mainwindow.h"
#include "addnewtorrentdialog.h" #include "addnewtorrentdialog.h"
#include "guiiconprovider.h" #include "guiiconprovider.h"
#include "mainwindow.h"
#include "pluginselectdlg.h" #include "pluginselectdlg.h"
#include "searchlistdelegate.h"
#include "searchsortmodel.h" #include "searchsortmodel.h"
#include "searchtab.h" #include "searchtab.h"
#include "ui_searchwidget.h" #include "ui_searchwidget.h"
#define SEARCHHISTORY_MAXSIZE 50 #define SEARCHHISTORY_MAXSIZE 50
#define URL_COLUMN 5 #define URL_COLUMN 5
namespace
{
QString statusIconName(SearchTab::Status st)
{
switch (st) {
case SearchTab::Status::Ongoing:
return QLatin1String("task-ongoing");
case SearchTab::Status::Finished:
return QLatin1String("task-complete");
case SearchTab::Status::Aborted:
return QLatin1String("task-reject");
case SearchTab::Status::Error:
return QLatin1String("task-attention");
case SearchTab::Status::NoResults:
return QLatin1String("task-attention");
default:
return QString();
}
}
}
SearchWidget::SearchWidget(MainWindow *mainWindow) SearchWidget::SearchWidget(MainWindow *mainWindow)
: QWidget(mainWindow) : QWidget(mainWindow)
, m_ui(new Ui::SearchWidget()) , m_ui(new Ui::SearchWidget())
, m_tabStatusChangedMapper(new QSignalMapper(this))
, m_mainWindow(mainWindow) , m_mainWindow(mainWindow)
, m_isNewQueryString(false) , m_isNewQueryString(false)
, m_noSearchResults(true)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@ -113,42 +130,41 @@ SearchWidget::SearchWidget(MainWindow *mainWindow)
m_ui->tabWidget->setIconSize(iconSize); m_ui->tabWidget->setIconSize(iconSize);
#endif #endif
connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab); connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab);
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::tabChanged);
m_searchEngine = new SearchEngine; connect(m_tabStatusChangedMapper, static_cast<void (QSignalMapper::*)(QWidget *)>(&QSignalMapper::mapped)
connect(m_searchEngine, &SearchEngine::searchStarted, this, &SearchWidget::searchStarted); , this, &SearchWidget::tabStatusChanged);
connect(m_searchEngine, &SearchEngine::newSearchResults, this, &SearchWidget::appendSearchResults);
connect(m_searchEngine, &SearchEngine::searchFinished, this, &SearchWidget::searchFinished);
connect(m_searchEngine, &SearchEngine::searchFailed, this, &SearchWidget::searchFailed);
connect(m_searchEngine, &SearchEngine::torrentFileDownloaded, this, &SearchWidget::addTorrentToSession);
m_searchManager = new SearchPluginManager;
const auto onPluginChanged = [this]() const auto onPluginChanged = [this]()
{ {
fillCatCombobox(); fillCatCombobox();
fillPluginComboBox(); fillPluginComboBox();
selectActivePage(); selectActivePage();
}; };
connect(m_searchEngine, &SearchEngine::pluginInstalled, this, onPluginChanged); connect(m_searchManager, &SearchPluginManager::pluginInstalled, this, onPluginChanged);
connect(m_searchEngine, &SearchEngine::pluginUninstalled, this, onPluginChanged); connect(m_searchManager, &SearchPluginManager::pluginUninstalled, this, onPluginChanged);
connect(m_searchEngine, &SearchEngine::pluginUpdated, this, onPluginChanged); connect(m_searchManager, &SearchPluginManager::pluginUpdated, this, onPluginChanged);
connect(m_searchEngine, &SearchEngine::pluginEnabled, this, onPluginChanged); connect(m_searchManager, &SearchPluginManager::pluginEnabled, this, onPluginChanged);
// Fill in category combobox // Fill in category combobox
onPluginChanged(); onPluginChanged();
connect(m_ui->m_searchPattern, &LineEdit::returnPressed, m_ui->searchButton, &QPushButton::click); connect(m_ui->m_searchPattern, &LineEdit::returnPressed, m_ui->searchButton, &QPushButton::click);
connect(m_ui->m_searchPattern, &LineEdit::textEdited, this, &SearchWidget::searchTextEdited); connect(m_ui->m_searchPattern, &LineEdit::textEdited, this, &SearchWidget::searchTextEdited);
connect(m_ui->selectPlugin, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SearchWidget::selectMultipleBox); connect(m_ui->selectPlugin, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged)
, this, &SearchWidget::selectMultipleBox);
} }
void SearchWidget::fillCatCombobox() void SearchWidget::fillCatCombobox()
{ {
m_ui->comboCategory->clear(); m_ui->comboCategory->clear();
m_ui->comboCategory->addItem(SearchEngine::categoryFullName("all"), QVariant("all")); m_ui->comboCategory->addItem(SearchPluginManager::categoryFullName("all"), QVariant("all"));
using QStrPair = QPair<QString, QString>; using QStrPair = QPair<QString, QString>;
QList<QStrPair> tmpList; QList<QStrPair> tmpList;
foreach (const QString &cat, m_searchEngine->supportedCategories()) foreach (const QString &cat, m_searchManager->supportedCategories())
tmpList << qMakePair(SearchEngine::categoryFullName(cat), cat); tmpList << qMakePair(SearchPluginManager::categoryFullName(cat), cat);
std::sort(tmpList.begin(), tmpList.end(), [](const QStrPair &l, const QStrPair &r) { return (QString::localeAwareCompare(l.first, r.first) < 0); }); std::sort(tmpList.begin(), tmpList.end(), [](const QStrPair &l, const QStrPair &r) { return (QString::localeAwareCompare(l.first, r.first) < 0); });
foreach (const QStrPair &p, tmpList) { foreach (const QStrPair &p, tmpList) {
@ -169,8 +185,8 @@ void SearchWidget::fillPluginComboBox()
using QStrPair = QPair<QString, QString>; using QStrPair = QPair<QString, QString>;
QList<QStrPair> tmpList; QList<QStrPair> tmpList;
foreach (const QString &name, m_searchEngine->enabledPlugins()) foreach (const QString &name, m_searchManager->enabledPlugins())
tmpList << qMakePair(m_searchEngine->pluginFullName(name), name); tmpList << qMakePair(m_searchManager->pluginFullName(name), name);
std::sort(tmpList.begin(), tmpList.end(), [](const QStrPair &l, const QStrPair &r) { return (l.first < r.first); } ); std::sort(tmpList.begin(), tmpList.end(), [](const QStrPair &l, const QStrPair &r) { return (l.first < r.first); } );
foreach (const QStrPair &p, tmpList) foreach (const QStrPair &p, tmpList)
@ -192,7 +208,7 @@ QString SearchWidget::selectedPlugin() const
void SearchWidget::selectActivePage() void SearchWidget::selectActivePage()
{ {
if (m_searchEngine->allPlugins().isEmpty()) { if (m_searchManager->allPlugins().isEmpty()) {
m_ui->stackedPages->setCurrentWidget(m_ui->emptyPage); m_ui->stackedPages->setCurrentWidget(m_ui->emptyPage);
m_ui->m_searchPattern->setEnabled(false); m_ui->m_searchPattern->setEnabled(false);
m_ui->comboCategory->setEnabled(false); m_ui->comboCategory->setEnabled(false);
@ -211,26 +227,13 @@ void SearchWidget::selectActivePage()
SearchWidget::~SearchWidget() SearchWidget::~SearchWidget()
{ {
qDebug("Search destruction"); qDebug("Search destruction");
delete m_searchEngine; delete m_searchManager;
delete m_ui; delete m_ui;
} }
void SearchWidget::downloadTorrent(const QString &siteUrl, const QString &url) void SearchWidget::updateButtons()
{
if (url.startsWith("bc://bt/", Qt::CaseInsensitive) || url.startsWith("magnet:", Qt::CaseInsensitive))
addTorrentToSession(url);
else
m_searchEngine->downloadTorrent(siteUrl, url);
}
void SearchWidget::tab_changed(int t)
{ {
//when we switch from a tab that is not empty to another that is empty the download button if (m_currentSearchTab && (m_currentSearchTab->visibleResultsCount() > 0)) {
//doesn't have to be available
if (t > -1) {
//-1 = no more tab
m_currentSearchTab = m_allTabs.at(m_ui->tabWidget->currentIndex());
if (m_currentSearchTab->getCurrentSearchListModel()->rowCount()) {
m_ui->downloadButton->setEnabled(true); m_ui->downloadButton->setEnabled(true);
m_ui->goToDescBtn->setEnabled(true); m_ui->goToDescBtn->setEnabled(true);
m_ui->copyURLBtn->setEnabled(true); m_ui->copyURLBtn->setEnabled(true);
@ -240,7 +243,14 @@ void SearchWidget::tab_changed(int t)
m_ui->goToDescBtn->setEnabled(false); m_ui->goToDescBtn->setEnabled(false);
m_ui->copyURLBtn->setEnabled(false); m_ui->copyURLBtn->setEnabled(false);
} }
} }
void SearchWidget::tabChanged(int index)
{
// when we switch from a tab that is not empty to another that is empty
// the download button doesn't have to be available
m_currentSearchTab = (index < 0 ? nullptr : m_allTabs.at(m_ui->tabWidget->currentIndex()));
updateButtons();
} }
void SearchWidget::selectMultipleBox(int index) void SearchWidget::selectMultipleBox(int index)
@ -250,17 +260,9 @@ void SearchWidget::selectMultipleBox(int index)
on_pluginsButton_clicked(); on_pluginsButton_clicked();
} }
void SearchWidget::addTorrentToSession(const QString &source)
{
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(source, this);
else
BitTorrent::Session::instance()->addTorrent(source);
}
void SearchWidget::on_pluginsButton_clicked() void SearchWidget::on_pluginsButton_clicked()
{ {
new PluginSelectDlg(m_searchEngine, this); new PluginSelectDlg(m_searchManager, this);
} }
void SearchWidget::searchTextEdited(QString) void SearchWidget::searchTextEdited(QString)
@ -275,11 +277,6 @@ void SearchWidget::giveFocusToSearchInput()
m_ui->m_searchPattern->setFocus(); m_ui->m_searchPattern->setFocus();
} }
QTabWidget *SearchWidget::searchTabs() const
{
return m_ui->tabWidget;
}
// Function called when we click on search button // Function called when we click on search button
void SearchWidget::on_searchButton_clicked() void SearchWidget::on_searchButton_clicked()
{ {
@ -288,9 +285,8 @@ void SearchWidget::on_searchButton_clicked()
return; return;
} }
if (m_searchEngine->isActive()) { if (m_activeSearchTab) {
m_searchEngine->cancelSearch(); m_activeSearchTab->cancelSearch();
if (!m_isNewQueryString) { if (!m_isNewQueryString) {
m_ui->searchButton->setText(tr("Search")); m_ui->searchButton->setText(tr("Search"));
return; return;
@ -306,173 +302,84 @@ void SearchWidget::on_searchButton_clicked()
return; return;
} }
// Tab Addition
m_currentSearchTab = new SearchTab(this);
m_activeSearchTab = m_currentSearchTab;
m_allTabs.append(m_currentSearchTab);
QString tabName = pattern;
tabName.replace(QRegExp("&{1}"), "&&");
m_ui->tabWidget->addTab(m_currentSearchTab, tabName);
m_ui->tabWidget->setCurrentWidget(m_currentSearchTab);
m_currentSearchTab->getCurrentSearchListProxy()->setNameFilter(pattern);
QStringList plugins; QStringList plugins;
if (selectedPlugin() == "all") plugins = m_searchEngine->allPlugins(); if (selectedPlugin() == "all") plugins = m_searchManager->allPlugins();
else if (selectedPlugin() == "enabled") plugins = m_searchEngine->enabledPlugins(); else if (selectedPlugin() == "enabled") plugins = m_searchManager->enabledPlugins();
else if (selectedPlugin() == "multi") plugins = m_searchEngine->enabledPlugins(); else if (selectedPlugin() == "multi") plugins = m_searchManager->enabledPlugins();
else plugins << selectedPlugin(); else plugins << selectedPlugin();
qDebug("Search with category: %s", qUtf8Printable(selectedCategory())); qDebug("Search with category: %s", qUtf8Printable(selectedCategory()));
// Update SearchEngine widgets
m_noSearchResults = true;
// Changing the text of the current label
m_activeSearchTab->updateResultsCount();
// Launch search // Launch search
m_searchEngine->startSearch(pattern, selectedCategory(), plugins); auto *searchHandler = m_searchManager->startSearch(pattern, selectedCategory(), plugins);
}
void SearchWidget::searchStarted() // Tab Addition
{ auto *newTab = new SearchTab(searchHandler, this);
// Update SearchEngine widgets m_allTabs.append(newTab);
m_activeSearchTab->setStatus(SearchTab::Status::Ongoing);
m_ui->searchButton->setText(tr("Stop"));
}
// Slot called when search is Finished
// Search can be finished for 3 reasons :
// Error | Stopped by user | Finished normally
void SearchWidget::searchFinished(bool cancelled)
{
if (m_mainWindow->isNotificationsEnabled() && (m_mainWindow->currentTabWidget() != this))
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has finished"));
if (m_activeSearchTab.isNull()) return; // The active tab was closed QString tabName = pattern;
tabName.replace(QRegExp("&{1}"), "&&");
m_ui->tabWidget->addTab(newTab, tabName);
m_ui->tabWidget->setCurrentWidget(newTab);
if (cancelled) connect(newTab, &SearchTab::resultsCountUpdated, this, &SearchWidget::resultsCountUpdated);
m_activeSearchTab->setStatus(SearchTab::Status::Aborted); connect(newTab, &SearchTab::statusChanged
else if (m_noSearchResults) , m_tabStatusChangedMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
m_activeSearchTab->setStatus(SearchTab::Status::NoResults); m_tabStatusChangedMapper->setMapping(newTab, newTab);
else
m_activeSearchTab->setStatus(SearchTab::Status::Finished);
m_activeSearchTab = 0; m_ui->searchButton->setText(tr("Stop"));
m_ui->searchButton->setText(tr("Search")); m_activeSearchTab = newTab;
tabStatusChanged(newTab);
} }
void SearchWidget::searchFailed() void SearchWidget::resultsCountUpdated()
{ {
if (m_mainWindow->isNotificationsEnabled() && (m_mainWindow->currentTabWidget() != this)) updateButtons();
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has failed"));
if (m_activeSearchTab.isNull()) return; // The active tab was closed
#ifdef Q_OS_WIN
m_activeSearchTab->setStatus(SearchTab::Status::Aborted);
#else
m_activeSearchTab->setStatus(SearchTab::Status::Error);
#endif
} }
void SearchWidget::appendSearchResults(const QList<SearchResult> &results) void SearchWidget::tabStatusChanged(QWidget *tab)
{ {
if (m_activeSearchTab.isNull()) { const int tabIndex = m_ui->tabWidget->indexOf(tab);
m_searchEngine->cancelSearch(); m_ui->tabWidget->setTabToolTip(tabIndex, tab->statusTip());
return; m_ui->tabWidget->setTabIcon(tabIndex, GuiIconProvider::instance()->getIcon(
} statusIconName(static_cast<SearchTab *>(tab)->status())));
Q_ASSERT(m_activeSearchTab);
QStandardItemModel *curModel = m_activeSearchTab->getCurrentSearchListModel(); if ((tab == m_activeSearchTab) && (m_activeSearchTab->status() != SearchTab::Status::Ongoing)) {
Q_ASSERT(curModel); Q_ASSERT(m_activeSearchTab->status() != SearchTab::Status::Ongoing);
foreach (const SearchResult &result, results) { if (m_mainWindow->isNotificationsEnabled() && (m_mainWindow->currentTabWidget() != this)) {
// Add item to search result list if (m_activeSearchTab->status() == SearchTab::Status::Error)
int row = curModel->rowCount(); m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has failed"));
curModel->insertRow(row); else
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has finished"));
curModel->setData(curModel->index(row, SearchSortModel::DL_LINK), result.fileUrl); // download URL
curModel->setData(curModel->index(row, SearchSortModel::NAME), result.fileName); // Name
curModel->setData(curModel->index(row, SearchSortModel::SIZE), result.fileSize); // Size
curModel->setData(curModel->index(row, SearchSortModel::SEEDS), result.nbSeeders); // Seeders
curModel->setData(curModel->index(row, SearchSortModel::LEECHES), result.nbLeechers); // Leechers
curModel->setData(curModel->index(row, SearchSortModel::ENGINE_URL), result.siteUrl); // Search site URL
curModel->setData(curModel->index(row, SearchSortModel::DESC_LINK), result.descrLink); // Description Link
} }
m_noSearchResults = false; m_activeSearchTab = nullptr;
m_activeSearchTab->updateResultsCount(); m_ui->searchButton->setText(tr("Search"));
}
// Enable clear & download buttons
m_ui->downloadButton->setEnabled(true);
m_ui->goToDescBtn->setEnabled(true);
m_ui->copyURLBtn->setEnabled(true);
} }
void SearchWidget::closeTab(int index) void SearchWidget::closeTab(int index)
{ {
// Search is run for active tab so if user decided to close it, then stop search SearchTab *tab = m_allTabs.takeAt(index);
if (!m_activeSearchTab.isNull() && index == m_ui->tabWidget->indexOf(m_activeSearchTab)) { if (tab == m_activeSearchTab)
qDebug("Closed active search Tab"); m_ui->searchButton->setText(tr("Search"));
if (m_searchEngine->isActive())
m_searchEngine->cancelSearch();
m_activeSearchTab = 0;
}
delete m_allTabs.takeAt(index);
if (!m_allTabs.size()) { delete tab;
m_ui->downloadButton->setEnabled(false);
m_ui->goToDescBtn->setEnabled(false);
m_ui->copyURLBtn->setEnabled(false);
}
} }
// Download selected items in search results list // Download selected items in search results list
void SearchWidget::on_downloadButton_clicked() void SearchWidget::on_downloadButton_clicked()
{ {
//QModelIndexList selectedIndexes = currentSearchTab->getCurrentTreeView()->selectionModel()->selectedIndexes(); m_allTabs.at(m_ui->tabWidget->currentIndex())->downloadTorrents();
QModelIndexList selectedIndexes =
m_allTabs.at(m_ui->tabWidget->currentIndex())->getCurrentTreeView()->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, selectedIndexes) {
if (index.column() == SearchSortModel::NAME)
m_allTabs.at(m_ui->tabWidget->currentIndex())->downloadItem(index);
}
} }
void SearchWidget::on_goToDescBtn_clicked() void SearchWidget::on_goToDescBtn_clicked()
{ {
QModelIndexList selectedIndexes = m_allTabs.at(m_ui->tabWidget->currentIndex())->openTorrentPages();
m_allTabs.at(m_ui->tabWidget->currentIndex())->getCurrentTreeView()->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, selectedIndexes) {
if (index.column() == SearchSortModel::NAME) {
QSortFilterProxyModel *model = m_allTabs.at(m_ui->tabWidget->currentIndex())->getCurrentSearchListProxy();
const QString descUrl = model->data(model->index(index.row(), SearchSortModel::DESC_LINK)).toString();
if (!descUrl.isEmpty())
QDesktopServices::openUrl(QUrl::fromEncoded(descUrl.toUtf8()));
}
}
} }
void SearchWidget::on_copyURLBtn_clicked() void SearchWidget::on_copyURLBtn_clicked()
{ {
QStringList urls; m_allTabs.at(m_ui->tabWidget->currentIndex())->copyTorrentURLs();
QModelIndexList selectedIndexes =
m_allTabs.at(m_ui->tabWidget->currentIndex())->getCurrentTreeView()->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, selectedIndexes) {
if (index.column() == SearchSortModel::NAME) {
QSortFilterProxyModel *model = m_allTabs.at(m_ui->tabWidget->currentIndex())->getCurrentSearchListProxy();
const QString descUrl = model->data(model->index(index.row(), SearchSortModel::DESC_LINK)).toString();
if (!descUrl.isEmpty())
urls << descUrl.toUtf8();
}
}
if (!urls.empty()) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(urls.join("\n"));
}
} }

41
src/gui/search/searchwidget.h

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -25,22 +25,19 @@
* modify file(s), you may extend this exception to your version of the file(s), * 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 * but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version. * exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/ */
#ifndef SEARCHWIDGET_H #pragma once
#define SEARCHWIDGET_H
#include <QList> #include <QList>
#include <QPointer> #include <QPointer>
#include <QWidget> #include <QWidget>
class QSignalMapper;
class QTabWidget; class QTabWidget;
class MainWindow; class MainWindow;
class SearchEngine; class SearchPluginManager;
struct SearchResult;
class SearchTab; class SearchTab;
namespace Ui namespace Ui
@ -48,56 +45,46 @@ namespace Ui
class SearchWidget; class SearchWidget;
} }
class SearchWidget: public QWidget class SearchWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(SearchWidget) Q_DISABLE_COPY(SearchWidget)
public: public:
explicit SearchWidget(MainWindow *mainWindow); explicit SearchWidget(MainWindow *mainWindow);
~SearchWidget(); ~SearchWidget() override;
void downloadTorrent(const QString &siteUrl, const QString &url);
void giveFocusToSearchInput(); void giveFocusToSearchInput();
QTabWidget* searchTabs() const;
private slots: private slots:
// Search slots
void tab_changed(int); //to prevent the use of the download button when the tab is empty
void on_searchButton_clicked(); void on_searchButton_clicked();
void on_downloadButton_clicked(); void on_downloadButton_clicked();
void on_goToDescBtn_clicked(); void on_goToDescBtn_clicked();
void on_copyURLBtn_clicked(); void on_copyURLBtn_clicked();
void on_pluginsButton_clicked(); void on_pluginsButton_clicked();
private:
void tabChanged(int index);
void closeTab(int index); void closeTab(int index);
void appendSearchResults(const QList<SearchResult> &results); void resultsCountUpdated();
void searchStarted(); void tabStatusChanged(QWidget *tab);
void searchFinished(bool cancelled);
void searchFailed();
void selectMultipleBox(int index); void selectMultipleBox(int index);
void addTorrentToSession(const QString &source);
void fillCatCombobox(); void fillCatCombobox();
void fillPluginComboBox(); void fillPluginComboBox();
void selectActivePage(); void selectActivePage();
void searchTextEdited(QString); void searchTextEdited(QString);
void updateButtons();
private:
QString selectedCategory() const; QString selectedCategory() const;
QString selectedPlugin() const; QString selectedPlugin() const;
Ui::SearchWidget *m_ui; Ui::SearchWidget *m_ui;
SearchEngine *m_searchEngine; SearchPluginManager *m_searchManager;
QSignalMapper *m_tabStatusChangedMapper;
QPointer<SearchTab> m_currentSearchTab; // Selected tab QPointer<SearchTab> m_currentSearchTab; // Selected tab
QPointer<SearchTab> m_activeSearchTab; // Tab with running search QPointer<SearchTab> m_activeSearchTab; // Tab with running search
QList<QPointer<SearchTab>> m_allTabs; // To store all tabs QList<SearchTab *> m_allTabs; // To store all tabs
MainWindow *m_mainWindow; MainWindow *m_mainWindow;
bool m_isNewQueryString; bool m_isNewQueryString;
bool m_noSearchResults;
QByteArray m_searchResultLineTruncated;
}; };
#endif // SEARCHWIDGET_H

Loading…
Cancel
Save