Thomas Piccirello
7 years ago
8 changed files with 464 additions and 17 deletions
@ -0,0 +1,367 @@
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com> |
||||
* |
||||
* 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 "searchcontroller.h" |
||||
|
||||
#include <QSharedPointer> |
||||
|
||||
#include "base/global.h" |
||||
#include "base/logger.h" |
||||
#include "base/search/searchhandler.h" |
||||
#include "base/utils/foreignapps.h" |
||||
#include "base/utils/random.h" |
||||
#include "base/utils/string.h" |
||||
#include "apierror.h" |
||||
|
||||
class SearchPluginManager; |
||||
|
||||
using SearchHandlerPtr = QSharedPointer<SearchHandler>; |
||||
using SearchHandlerDict = QMap<int, SearchHandlerPtr>; |
||||
|
||||
namespace |
||||
{ |
||||
const QLatin1String ACTIVE_SEARCHES("activeSearches"); |
||||
const QLatin1String SEARCH_HANDLERS("searchHandlers"); |
||||
|
||||
void removeActiveSearch(ISession *session, const int id) |
||||
{ |
||||
auto activeSearches = session->getData<QSet<int>>(ACTIVE_SEARCHES); |
||||
if (activeSearches.remove(id) > 0) |
||||
session->setData(ACTIVE_SEARCHES, QVariant::fromValue(activeSearches)); |
||||
} |
||||
} |
||||
|
||||
void SearchController::startAction() |
||||
{ |
||||
checkParams({"pattern", "category", "plugins"}); |
||||
|
||||
if (!Utils::ForeignApps::pythonInfo().isValid()) |
||||
throw APIError(APIErrorType::Conflict, "Python must be installed to use the Search Engine."); |
||||
|
||||
const QString pattern = params()["pattern"].trimmed(); |
||||
const QString category = params()["category"].trimmed(); |
||||
const QStringList plugins = params()["plugins"].split('|'); |
||||
|
||||
QStringList pluginsToUse; |
||||
if (plugins.size() == 1) { |
||||
const QString pluginsLower = plugins[0].toLower(); |
||||
if (pluginsLower == "all") |
||||
pluginsToUse = SearchPluginManager::instance()->allPlugins(); |
||||
else if ((pluginsLower == "enabled") || (pluginsLower == "multi")) |
||||
pluginsToUse = SearchPluginManager::instance()->enabledPlugins(); |
||||
else |
||||
pluginsToUse << plugins; |
||||
} |
||||
else { |
||||
pluginsToUse << plugins; |
||||
} |
||||
|
||||
ISession *const session = sessionManager()->session(); |
||||
auto activeSearches = session->getData<QSet<int>>(ACTIVE_SEARCHES); |
||||
if (activeSearches.size() >= MAX_CONCURRENT_SEARCHES) |
||||
throw APIError(APIErrorType::Conflict, QString("Unable to create more than %1 concurrent searches.").arg(MAX_CONCURRENT_SEARCHES)); |
||||
|
||||
const auto id = generateSearchId(); |
||||
const SearchHandlerPtr searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)}; |
||||
QObject::connect(searchHandler.data(), &SearchHandler::searchFinished, this, [session, id, this]() { searchFinished(session, id); }); |
||||
QObject::connect(searchHandler.data(), &SearchHandler::searchFailed, this, [session, id, this]() { searchFailed(session, id); }); |
||||
|
||||
auto searchHandlers = session->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
searchHandlers.insert(id, searchHandler); |
||||
session->setData(SEARCH_HANDLERS, QVariant::fromValue(searchHandlers)); |
||||
|
||||
activeSearches.insert(id); |
||||
session->setData(ACTIVE_SEARCHES, QVariant::fromValue(activeSearches)); |
||||
|
||||
const QJsonObject result = {{"id", id}}; |
||||
setResult(result); |
||||
} |
||||
|
||||
void SearchController::stopAction() |
||||
{ |
||||
checkParams({"id"}); |
||||
|
||||
const int id = params()["id"].toInt(); |
||||
ISession *const session = sessionManager()->session(); |
||||
|
||||
auto searchHandlers = session->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
if (!searchHandlers.contains(id)) |
||||
throw APIError(APIErrorType::NotFound); |
||||
|
||||
const SearchHandlerPtr searchHandler = searchHandlers[id]; |
||||
|
||||
if (searchHandler->isActive()) { |
||||
searchHandler->cancelSearch(); |
||||
removeActiveSearch(session, id); |
||||
} |
||||
} |
||||
|
||||
void SearchController::statusAction() |
||||
{ |
||||
const int id = params()["id"].toInt(); |
||||
|
||||
const auto searchHandlers = sessionManager()->session()->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
if ((id != 0) && !searchHandlers.contains(id)) |
||||
throw APIError(APIErrorType::NotFound); |
||||
|
||||
QJsonArray statusArray; |
||||
const QList<int> searchIds {(id == 0) ? searchHandlers.keys() : QList<int> {id}}; |
||||
|
||||
for (const int searchId : searchIds) { |
||||
const SearchHandlerPtr searchHandler = searchHandlers[searchId]; |
||||
statusArray << QJsonObject { |
||||
{"id", searchId}, |
||||
{"status", searchHandler->isActive() ? "Running" : "Stopped"}, |
||||
{"total", searchHandler->results().size()} |
||||
}; |
||||
} |
||||
|
||||
setResult(statusArray); |
||||
} |
||||
|
||||
void SearchController::resultsAction() |
||||
{ |
||||
checkParams({"id"}); |
||||
|
||||
const int id = params()["id"].toInt(); |
||||
int limit = params()["limit"].toInt(); |
||||
int offset = params()["offset"].toInt(); |
||||
|
||||
const auto searchHandlers = sessionManager()->session()->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
if (!searchHandlers.contains(id)) |
||||
throw APIError(APIErrorType::NotFound); |
||||
|
||||
const SearchHandlerPtr searchHandler = searchHandlers[id]; |
||||
const QList<SearchResult> searchResults = searchHandler->results(); |
||||
const int size = searchResults.size(); |
||||
|
||||
if (offset > size) |
||||
throw APIError(APIErrorType::Conflict, tr("Offset is out of range")); |
||||
|
||||
// normalize values
|
||||
if (offset < 0) |
||||
offset = size + offset; |
||||
if (offset < 0) // check again
|
||||
throw APIError(APIErrorType::Conflict, tr("Offset is out of range")); |
||||
if (limit <= 0) |
||||
limit = -1; |
||||
|
||||
if ((limit > 0) || (offset > 0)) |
||||
setResult(getResults(searchResults.mid(offset, limit), searchHandler->isActive(), size)); |
||||
else |
||||
setResult(getResults(searchResults, searchHandler->isActive(), size)); |
||||
} |
||||
|
||||
void SearchController::deleteAction() |
||||
{ |
||||
checkParams({"id"}); |
||||
|
||||
const int id = params()["id"].toInt(); |
||||
ISession *const session = sessionManager()->session(); |
||||
|
||||
auto searchHandlers = session->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
if (!searchHandlers.contains(id)) |
||||
throw APIError(APIErrorType::NotFound); |
||||
|
||||
const SearchHandlerPtr searchHandler = searchHandlers[id]; |
||||
searchHandler->cancelSearch(); |
||||
searchHandlers.remove(id); |
||||
session->setData(SEARCH_HANDLERS, QVariant::fromValue(searchHandlers)); |
||||
|
||||
removeActiveSearch(session, id); |
||||
} |
||||
|
||||
void SearchController::categoriesAction() |
||||
{ |
||||
QStringList categories; |
||||
const QString name = params()["pluginName"].trimmed(); |
||||
|
||||
categories << SearchPluginManager::categoryFullName("all"); |
||||
for (const QString &category : copyAsConst(SearchPluginManager::instance()->getPluginCategories(name))) |
||||
categories << SearchPluginManager::categoryFullName(category); |
||||
|
||||
const QJsonArray result = QJsonArray::fromStringList(categories); |
||||
setResult(result); |
||||
} |
||||
|
||||
void SearchController::pluginsAction() |
||||
{ |
||||
const QStringList allPlugins = SearchPluginManager::instance()->allPlugins(); |
||||
setResult(getPluginsInfo(allPlugins)); |
||||
} |
||||
|
||||
void SearchController::installPluginAction() |
||||
{ |
||||
checkParams({"sources"}); |
||||
|
||||
const QStringList sources = params()["sources"].split('|'); |
||||
for (const QString &source : sources) |
||||
SearchPluginManager::instance()->installPlugin(source); |
||||
} |
||||
|
||||
void SearchController::uninstallPluginAction() |
||||
{ |
||||
checkParams({"names"}); |
||||
|
||||
const QStringList names = params()["names"].split('|'); |
||||
for (const QString &name : names) |
||||
SearchPluginManager::instance()->uninstallPlugin(name.trimmed()); |
||||
} |
||||
|
||||
void SearchController::enablePluginAction() |
||||
{ |
||||
checkParams({"names", "enable"}); |
||||
|
||||
const QStringList names = params()["names"].split('|'); |
||||
const bool enable = Utils::String::parseBool(params()["enable"].trimmed(), false); |
||||
|
||||
for (const QString &name : names) |
||||
SearchPluginManager::instance()->enablePlugin(name.trimmed(), enable); |
||||
} |
||||
|
||||
void SearchController::updatePluginsAction() |
||||
{ |
||||
SearchPluginManager *const pluginManager = SearchPluginManager::instance(); |
||||
|
||||
connect(pluginManager, &SearchPluginManager::checkForUpdatesFinished, this, &SearchController::checkForUpdatesFinished); |
||||
connect(pluginManager, &SearchPluginManager::checkForUpdatesFailed, this, &SearchController::checkForUpdatesFailed); |
||||
pluginManager->checkForUpdates(); |
||||
} |
||||
|
||||
void SearchController::checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo) |
||||
{ |
||||
if (updateInfo.isEmpty()) { |
||||
LogMsg(tr("All plugins are already up to date."), Log::INFO); |
||||
return; |
||||
} |
||||
|
||||
LogMsg(tr("Updating %1 plugins").arg(updateInfo.size()), Log::INFO); |
||||
|
||||
SearchPluginManager *const pluginManager = SearchPluginManager::instance(); |
||||
for (const QString &pluginName : updateInfo.keys()) { |
||||
LogMsg(tr("Updating plugin %1").arg(pluginName), Log::INFO); |
||||
pluginManager->updatePlugin(pluginName); |
||||
} |
||||
} |
||||
|
||||
void SearchController::checkForUpdatesFailed(const QString &reason) |
||||
{ |
||||
LogMsg(tr("Failed to check for plugin updates: %1").arg(reason), Log::INFO); |
||||
} |
||||
|
||||
void SearchController::searchFinished(ISession *session, const int id) |
||||
{ |
||||
removeActiveSearch(session, id); |
||||
} |
||||
|
||||
void SearchController::searchFailed(ISession *session, const int id) |
||||
{ |
||||
removeActiveSearch(session, id); |
||||
} |
||||
|
||||
int SearchController::generateSearchId() const |
||||
{ |
||||
const auto searchHandlers = sessionManager()->session()->getData<SearchHandlerDict>(SEARCH_HANDLERS); |
||||
|
||||
while (true) |
||||
{ |
||||
const auto id = Utils::Random::rand(1, INT_MAX); |
||||
if (!searchHandlers.contains(id)) |
||||
return id; |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* Returns the search results in JSON format. |
||||
* |
||||
* The return value is an object with a status and an array of dictionaries. |
||||
* The dictionary keys are: |
||||
* - "fileName" |
||||
* - "fileUrl" |
||||
* - "fileSize" |
||||
* - "nbSeeders" |
||||
* - "nbLeechers" |
||||
* - "siteUrl" |
||||
* - "descrLink" |
||||
*/ |
||||
QJsonObject SearchController::getResults(const QList<SearchResult> &searchResults, const bool isSearchActive, const int totalResults) const |
||||
{ |
||||
QJsonArray searchResultsArray; |
||||
for (const SearchResult &searchResult : searchResults) { |
||||
searchResultsArray << QJsonObject { |
||||
{"fileName", searchResult.fileName}, |
||||
{"fileUrl", searchResult.fileUrl}, |
||||
{"fileSize", searchResult.fileSize}, |
||||
{"nbSeeders", searchResult.nbSeeders}, |
||||
{"nbLeechers", searchResult.nbLeechers}, |
||||
{"siteUrl", searchResult.siteUrl}, |
||||
{"descrLink", searchResult.descrLink} |
||||
}; |
||||
} |
||||
|
||||
const QJsonObject result = { |
||||
{"status", isSearchActive ? "Running" : "Stopped"}, |
||||
{"results", searchResultsArray}, |
||||
{"total", totalResults} |
||||
}; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/**
|
||||
* Returns the search plugins in JSON format. |
||||
* |
||||
* The return value is an array of dictionaries. |
||||
* The dictionary keys are: |
||||
* - "name" |
||||
* - "version" |
||||
* - "fullName" |
||||
* - "url" |
||||
* - "supportedCategories" |
||||
* - "iconPath" |
||||
* - "enabled" |
||||
*/ |
||||
QJsonArray SearchController::getPluginsInfo(const QStringList &plugins) const |
||||
{ |
||||
QJsonArray pluginsArray; |
||||
|
||||
for (const QString &plugin : plugins) { |
||||
const PluginInfo *const pluginInfo = SearchPluginManager::instance()->pluginInfo(plugin); |
||||
|
||||
pluginsArray << QJsonObject { |
||||
{"name", pluginInfo->name}, |
||||
{"version", QString(pluginInfo->version)}, |
||||
{"fullName", pluginInfo->fullName}, |
||||
{"url", pluginInfo->url}, |
||||
{"supportedCategories", QJsonArray::fromStringList(pluginInfo->supportedCategories)}, |
||||
{"enabled", pluginInfo->enabled} |
||||
}; |
||||
} |
||||
|
||||
return pluginsArray; |
||||
} |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com> |
||||
* |
||||
* 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 <QHash> |
||||
#include <QJsonArray> |
||||
#include <QJsonObject> |
||||
#include <QList> |
||||
#include <QString> |
||||
|
||||
#include "base/search/searchpluginmanager.h" |
||||
#include "apicontroller.h" |
||||
#include "isessionmanager.h" |
||||
|
||||
struct SearchResult; |
||||
|
||||
class SearchController : public APIController |
||||
{ |
||||
Q_OBJECT |
||||
Q_DISABLE_COPY(SearchController) |
||||
|
||||
public: |
||||
using APIController::APIController; |
||||
|
||||
private slots: |
||||
void startAction(); |
||||
void stopAction(); |
||||
void statusAction(); |
||||
void resultsAction(); |
||||
void deleteAction(); |
||||
void categoriesAction(); |
||||
void pluginsAction(); |
||||
void installPluginAction(); |
||||
void uninstallPluginAction(); |
||||
void enablePluginAction(); |
||||
void updatePluginsAction(); |
||||
|
||||
private: |
||||
const int MAX_CONCURRENT_SEARCHES = 5; |
||||
|
||||
void checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo); |
||||
void checkForUpdatesFailed(const QString &reason); |
||||
void searchFinished(ISession *session, const int id); |
||||
void searchFailed(ISession *session, const int id); |
||||
int generateSearchId() const; |
||||
QJsonObject getResults(const QList<SearchResult> &searchResults, bool isSearchActive, int totalResults) const; |
||||
QJsonArray getPluginsInfo(const QStringList &plugins) const; |
||||
}; |
Loading…
Reference in new issue