mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-11 15:27:54 +00:00
commit
b26eb3d146
@ -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
|
||||||
|
@ -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
Normal file
66
src/base/search/searchdownloadhandler.cpp
Normal file
@ -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
Normal file
53
src/base/search/searchdownloadhandler.h
Normal file
@ -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
Normal file
199
src/base/search/searchhandler.cpp
Normal file
@ -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
Normal file
90
src/base/search/searchhandler.h
Normal file
@ -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;
|
||||||
|
};
|
@ -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,73 +27,76 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QDomDocument>
|
#include "searchpluginmanager.h"
|
||||||
#include <QDomNode>
|
|
||||||
#include <QDomElement>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QProcess>
|
#include <QDomDocument>
|
||||||
#include <QDebug>
|
#include <QDomElement>
|
||||||
|
#include <QDomNode>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
#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"
|
||||||
|
#include "base/utils/fs.h"
|
||||||
|
#include "base/utils/misc.h"
|
||||||
|
#include "searchdownloadhandler.h"
|
||||||
|
#include "searchhandler.h"
|
||||||
|
|
||||||
enum SearchResultColumn
|
namespace
|
||||||
{
|
{
|
||||||
PL_DL_LINK,
|
inline void removePythonScriptIfExists(const QString &scriptPath)
|
||||||
PL_NAME,
|
|
||||||
PL_SIZE,
|
|
||||||
PL_SEEDS,
|
|
||||||
PL_LEECHS,
|
|
||||||
PL_ENGINE_URL,
|
|
||||||
PL_DESC_LINK,
|
|
||||||
NB_PLUGIN_COLUMNS
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void removePythonScriptIfExists(const QString &scriptPath)
|
|
||||||
{
|
{
|
||||||
Utils::Fs::forceRemove(scriptPath);
|
Utils::Fs::forceRemove(scriptPath);
|
||||||
Utils::Fs::forceRemove(scriptPath + "c");
|
Utils::Fs::forceRemove(scriptPath + "c");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const QHash<QString, QString> SearchEngine::m_categoryNames = SearchEngine::initializeCategoryNames();
|
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
|
||||||
|
|
||||||
SearchEngine::SearchEngine()
|
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")}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(!m_instance); // only one instance is allowed
|
||||||
|
m_instance = this;
|
||||||
|
|
||||||
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
|
SearchPluginManager *SearchPluginManager::instance()
|
||||||
|
{
|
||||||
|
return m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +107,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 +122,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);
|
return m_plugins.value(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SearchEngine::isActive() const
|
void SearchPluginManager::enablePlugin(const QString &name, bool enabled)
|
||||||
{
|
|
||||||
return (m_searchProcess->state() != QProcess::NotRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SearchEngine::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 +146,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 +160,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 +178,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 +227,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 +244,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 +258,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)
|
SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins)
|
||||||
{
|
{
|
||||||
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)
|
|
||||||
{
|
|
||||||
// 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;
|
return new SearchHandler {pattern, category, usedPlugins, this};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload environment variables (proxy)
|
QString SearchPluginManager::categoryFullName(const QString &categoryName)
|
||||||
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
|
|
||||||
|
|
||||||
QStringList params;
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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 +308,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 +330,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 +340,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 +406,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 +466,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 +501,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 +511,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 +539,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,25 @@ 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;
|
||||||
|
|
||||||
|
static SearchPluginManager *instance();
|
||||||
|
|
||||||
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 +75,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 +95,24 @@ 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);
|
||||||
static QString pluginPath(const QString &name);
|
void versionInfoDownloadFailed(const QString &url, const QString &reason);
|
||||||
static QHash<QString, QString> initializeCategoryNames();
|
void pluginDownloaded(const QString &url, QString filePath);
|
||||||
|
void pluginDownloadFailed(const QString &url, const QString &reason);
|
||||||
|
|
||||||
|
static QString pluginPath(const QString &name);
|
||||||
|
|
||||||
|
static QPointer<SearchPluginManager> m_instance;
|
||||||
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
|
|
@ -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)) {
|
||||||
|
@ -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;
|
||||||
|
@ -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 <QApplication>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QTreeView>
|
|
||||||
#include <QStandardItemModel>
|
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QTableView>
|
|
||||||
|
|
||||||
#include "base/utils/misc.h"
|
|
||||||
#include "base/preferences.h"
|
|
||||||
#include "base/settingvalue.h"
|
|
||||||
#include "guiiconprovider.h"
|
|
||||||
#include "searchsortmodel.h"
|
|
||||||
#include "searchlistdelegate.h"
|
|
||||||
#include "searchwidget.h"
|
|
||||||
#include "searchtab.h"
|
#include "searchtab.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QTableView>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.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/utils/misc.h"
|
||||||
|
#include "addnewtorrentdialog.h"
|
||||||
|
#include "guiiconprovider.h"
|
||||||
|
#include "searchlistdelegate.h"
|
||||||
|
#include "searchsortmodel.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,12 +162,10 @@ 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
|
||||||
@ -158,21 +173,6 @@ QHeaderView* SearchTab::header() const
|
|||||||
return m_ui->resultsBrowser->header();
|
return m_ui->resultsBrowser->header();
|
||||||
}
|
}
|
||||||
|
|
||||||
QTreeView* SearchTab::getCurrentTreeView() const
|
|
||||||
{
|
|
||||||
return m_ui->resultsBrowser;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchSortModel* SearchTab::getCurrentSearchListProxy() const
|
|
||||||
{
|
|
||||||
return m_proxyModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStandardItemModel* SearchTab::getCurrentSearchListModel() const
|
|
||||||
{
|
|
||||||
return m_searchListModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the color of a row in data model
|
// Set the color of a row in data model
|
||||||
void SearchTab::setRowColor(int row, const QColor &color)
|
void SearchTab::setRowColor(int row, const QColor &color)
|
||||||
{
|
{
|
||||||
@ -188,37 +188,112 @@ SearchTab::Status SearchTab::status() const
|
|||||||
return m_status;
|
return m_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SearchTab::visibleResultsCount() const
|
||||||
|
{
|
||||||
|
return m_proxyModel->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTab::cancelSearch()
|
||||||
|
{
|
||||||
|
m_searchHandler->cancelSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTab::downloadTorrents()
|
||||||
|
{
|
||||||
|
const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
|
||||||
|
for (const QModelIndex &rowIndex : rows)
|
||||||
|
downloadTorrent(rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTab::openTorrentPages()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
@ -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,16 +36,17 @@
|
|||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -57,27 +56,16 @@ namespace Ui
|
|||||||
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
|
|
||||||
|
@ -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 <QMessageBox>
|
|
||||||
#include <QSystemTrayIcon>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QMimeData>
|
|
||||||
#include <QStandardItemModel>
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QTreeView>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QSignalMapper>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QTreeView>
|
||||||
|
|
||||||
#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,42 @@ 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);
|
|
||||||
|
|
||||||
|
// NOTE: Although SearchManager is Application-wide component now, we still create it the legacy way.
|
||||||
|
auto *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(searchManager, &SearchPluginManager::pluginInstalled, this, onPluginChanged);
|
||||||
connect(m_searchEngine, &SearchEngine::pluginUninstalled, this, onPluginChanged);
|
connect(searchManager, &SearchPluginManager::pluginUninstalled, this, onPluginChanged);
|
||||||
connect(m_searchEngine, &SearchEngine::pluginUpdated, this, onPluginChanged);
|
connect(searchManager, &SearchPluginManager::pluginUpdated, this, onPluginChanged);
|
||||||
connect(m_searchEngine, &SearchEngine::pluginEnabled, this, onPluginChanged);
|
connect(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, SearchPluginManager::instance()->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 +186,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, SearchPluginManager::instance()->enabledPlugins())
|
||||||
tmpList << qMakePair(m_searchEngine->pluginFullName(name), name);
|
tmpList << qMakePair(SearchPluginManager::instance()->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 +209,7 @@ QString SearchWidget::selectedPlugin() const
|
|||||||
|
|
||||||
void SearchWidget::selectActivePage()
|
void SearchWidget::selectActivePage()
|
||||||
{
|
{
|
||||||
if (m_searchEngine->allPlugins().isEmpty()) {
|
if (SearchPluginManager::instance()->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 +228,13 @@ void SearchWidget::selectActivePage()
|
|||||||
SearchWidget::~SearchWidget()
|
SearchWidget::~SearchWidget()
|
||||||
{
|
{
|
||||||
qDebug("Search destruction");
|
qDebug("Search destruction");
|
||||||
delete m_searchEngine;
|
delete SearchPluginManager::instance();
|
||||||
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))
|
if (m_currentSearchTab && (m_currentSearchTab->visibleResultsCount() > 0)) {
|
||||||
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
|
|
||||||
//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);
|
||||||
@ -241,6 +245,13 @@ void SearchWidget::tab_changed(int t)
|
|||||||
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 +261,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(SearchPluginManager::instance(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchWidget::searchTextEdited(QString)
|
void SearchWidget::searchTextEdited(QString)
|
||||||
@ -275,11 +278,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 +286,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 +303,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 = SearchPluginManager::instance()->allPlugins();
|
||||||
else if (selectedPlugin() == "enabled") plugins = m_searchEngine->enabledPlugins();
|
else if (selectedPlugin() == "enabled") plugins = SearchPluginManager::instance()->enabledPlugins();
|
||||||
else if (selectedPlugin() == "multi") plugins = m_searchEngine->enabledPlugins();
|
else if (selectedPlugin() == "multi") plugins = SearchPluginManager::instance()->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 = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins);
|
||||||
}
|
|
||||||
|
// Tab Addition
|
||||||
|
auto *newTab = new SearchTab(searchHandler, this);
|
||||||
|
m_allTabs.append(newTab);
|
||||||
|
|
||||||
|
QString tabName = pattern;
|
||||||
|
tabName.replace(QRegExp("&{1}"), "&&");
|
||||||
|
m_ui->tabWidget->addTab(newTab, tabName);
|
||||||
|
m_ui->tabWidget->setCurrentWidget(newTab);
|
||||||
|
|
||||||
|
connect(newTab, &SearchTab::resultsCountUpdated, this, &SearchWidget::resultsCountUpdated);
|
||||||
|
connect(newTab, &SearchTab::statusChanged
|
||||||
|
, m_tabStatusChangedMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
|
||||||
|
m_tabStatusChangedMapper->setMapping(newTab, newTab);
|
||||||
|
|
||||||
void SearchWidget::searchStarted()
|
|
||||||
{
|
|
||||||
// Update SearchEngine widgets
|
|
||||||
m_activeSearchTab->setStatus(SearchTab::Status::Ongoing);
|
|
||||||
m_ui->searchButton->setText(tr("Stop"));
|
m_ui->searchButton->setText(tr("Stop"));
|
||||||
|
m_activeSearchTab = newTab;
|
||||||
|
tabStatusChanged(newTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot called when search is Finished
|
void SearchWidget::resultsCountUpdated()
|
||||||
// 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))
|
updateButtons();
|
||||||
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has finished"));
|
}
|
||||||
|
|
||||||
if (m_activeSearchTab.isNull()) return; // The active tab was closed
|
void SearchWidget::tabStatusChanged(QWidget *tab)
|
||||||
|
{
|
||||||
|
const int tabIndex = m_ui->tabWidget->indexOf(tab);
|
||||||
|
m_ui->tabWidget->setTabToolTip(tabIndex, tab->statusTip());
|
||||||
|
m_ui->tabWidget->setTabIcon(tabIndex, GuiIconProvider::instance()->getIcon(
|
||||||
|
statusIconName(static_cast<SearchTab *>(tab)->status())));
|
||||||
|
|
||||||
if (cancelled)
|
if ((tab == m_activeSearchTab) && (m_activeSearchTab->status() != SearchTab::Status::Ongoing)) {
|
||||||
m_activeSearchTab->setStatus(SearchTab::Status::Aborted);
|
Q_ASSERT(m_activeSearchTab->status() != SearchTab::Status::Ongoing);
|
||||||
else if (m_noSearchResults)
|
|
||||||
m_activeSearchTab->setStatus(SearchTab::Status::NoResults);
|
if (m_mainWindow->isNotificationsEnabled() && (m_mainWindow->currentTabWidget() != this)) {
|
||||||
|
if (m_activeSearchTab->status() == SearchTab::Status::Error)
|
||||||
|
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has failed"));
|
||||||
else
|
else
|
||||||
m_activeSearchTab->setStatus(SearchTab::Status::Finished);
|
m_mainWindow->showNotificationBaloon(tr("Search Engine"), tr("Search has finished"));
|
||||||
|
}
|
||||||
|
|
||||||
m_activeSearchTab = 0;
|
m_activeSearchTab = nullptr;
|
||||||
m_ui->searchButton->setText(tr("Search"));
|
m_ui->searchButton->setText(tr("Search"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchWidget::searchFailed()
|
|
||||||
{
|
|
||||||
if (m_mainWindow->isNotificationsEnabled() && (m_mainWindow->currentTabWidget() != this))
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (m_activeSearchTab.isNull()) {
|
|
||||||
m_searchEngine->cancelSearch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(m_activeSearchTab);
|
|
||||||
|
|
||||||
QStandardItemModel *curModel = m_activeSearchTab->getCurrentSearchListModel();
|
|
||||||
Q_ASSERT(curModel);
|
|
||||||
|
|
||||||
foreach (const SearchResult &result, results) {
|
|
||||||
// Add item to search result list
|
|
||||||
int row = curModel->rowCount();
|
|
||||||
curModel->insertRow(row);
|
|
||||||
|
|
||||||
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->updateResultsCount();
|
|
||||||
|
|
||||||
// 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);
|
delete tab;
|
||||||
|
|
||||||
if (!m_allTabs.size()) {
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,18 @@
|
|||||||
* 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;
|
|
||||||
struct SearchResult;
|
|
||||||
class SearchTab;
|
class SearchTab;
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
@ -55,49 +51,38 @@ class SearchWidget: public QWidget
|
|||||||
|
|
||||||
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;
|
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…
Reference in New Issue
Block a user