Propagate error messages from search engine

This will be useful for development and debugging issues. The messages will be outputted to both console and qbt logger.
Note that having stderr messages does not always mean the operation failed but there might be some (non-fatal) issues.

PR #23293.
Closes #6553. Closes #22381. Closes #23052.
This commit is contained in:
Chocobo1
2025-09-27 15:42:51 +08:00
committed by GitHub
parent 3de2a9f486
commit 10b879bdaf
8 changed files with 85 additions and 27 deletions

View File

@@ -28,9 +28,11 @@
#include "searchdownloadhandler.h" #include "searchdownloadhandler.h"
#include <QtLogging>
#include <QProcess> #include <QProcess>
#include "base/global.h" #include "base/global.h"
#include "base/logger.h"
#include "base/path.h" #include "base/path.h"
#include "base/utils/foreignapps.h" #include "base/utils/foreignapps.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
@@ -38,6 +40,8 @@
SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager) SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager)
: QObject(manager) : QObject(manager)
, m_pluginName {pluginName}
, m_url {url}
, m_manager {manager} , m_manager {manager}
, m_downloadProcess {new QProcess(this)} , m_downloadProcess {new QProcess(this)}
{ {
@@ -58,10 +62,17 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
m_downloadProcess->start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly); m_downloadProcess->start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly);
} }
void SearchDownloadHandler::downloadProcessFinished(int exitcode) void SearchDownloadHandler::downloadProcessFinished(const int exitcode)
{ {
QString path; const auto errMsg = QString::fromUtf8(m_downloadProcess->readAllStandardError()).trimmed();
if (!errMsg.isEmpty())
{
qWarning("%s", qUtf8Printable(errMsg));
LogMsg(tr("Error occurred when downloading torrent via search engine. Engine: \"%1\". URL: \"%2\". Error: \"%3\".")
.arg(m_pluginName, m_url, errMsg), Log::WARNING);
}
QString path;
if ((exitcode == 0) && (m_downloadProcess->exitStatus() == QProcess::NormalExit)) if ((exitcode == 0) && (m_downloadProcess->exitStatus() == QProcess::NormalExit))
{ {
const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed(); const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed();
@@ -70,5 +81,5 @@ void SearchDownloadHandler::downloadProcessFinished(int exitcode)
path = parts[0].toString(); path = parts[0].toString();
} }
emit downloadFinished(path); emit downloadFinished(path, errMsg);
} }

View File

@@ -29,6 +29,7 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QString>
class QProcess; class QProcess;
@@ -44,11 +45,13 @@ class SearchDownloadHandler : public QObject
SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager); SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager);
signals: signals:
void downloadFinished(const QString &path); void downloadFinished(const QString &path, const QString &errorMessage);
private: private:
void downloadProcessFinished(int exitcode); void downloadProcessFinished(int exitcode);
QString m_pluginName;
QString m_url;
SearchPluginManager *m_manager = nullptr; SearchPluginManager *m_manager = nullptr;
QProcess *m_downloadProcess = nullptr; QProcess *m_downloadProcess = nullptr;
}; };

View File

@@ -31,12 +31,14 @@
#include <chrono> #include <chrono>
#include <QtLogging>
#include <QList> #include <QList>
#include <QMetaObject> #include <QMetaObject>
#include <QProcess> #include <QProcess>
#include <QTimer> #include <QTimer>
#include "base/global.h" #include "base/global.h"
#include "base/logger.h"
#include "base/path.h" #include "base/path.h"
#include "base/utils/bytearray.h" #include "base/utils/bytearray.h"
#include "base/utils/foreignapps.h" #include "base/utils/foreignapps.h"
@@ -59,6 +61,26 @@ namespace
PL_PUB_DATE, PL_PUB_DATE,
NB_PLUGIN_COLUMNS NB_PLUGIN_COLUMNS
}; };
QString toString(const QProcess::ProcessError error)
{
switch (error)
{
case QProcess::FailedToStart:
return SearchHandler::tr("Process failed to start");
case QProcess::Crashed:
return SearchHandler::tr("Process crashed");
case QProcess::Timedout:
return SearchHandler::tr("Process timed out");
case QProcess::WriteError:
return SearchHandler::tr("Process write error");
case QProcess::ReadError:
return SearchHandler::tr("Process read error");
case QProcess::UnknownError:
return SearchHandler::tr("Process unknown error");
}
return {};
};
} }
SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager) SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
@@ -86,7 +108,16 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
}; };
m_searchProcess->setArguments(params + m_pattern.split(u' ')); m_searchProcess->setArguments(params + m_pattern.split(u' '));
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed); connect(m_searchProcess, &QProcess::errorOccurred, this, [this](const QProcess::ProcessError error)
{
if (!m_searchCancelled)
{
const auto errMsg = toString(error);
LogMsg(tr("Search process failed. Search query: \"%1\". Category: \"%2\". Engines: \"%3\". Error: \"%4\".")
.arg(m_pattern, m_category, m_usedPlugins.join(u", "), errMsg), Log::WARNING);
emit searchFailed(errMsg);
}
});
connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchHandler::readSearchOutput); connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchHandler::readSearchOutput);
connect(m_searchProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished) connect(m_searchProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
, this, &SearchHandler::processFinished); , this, &SearchHandler::processFinished);
@@ -127,12 +158,20 @@ void SearchHandler::processFinished(const int exitcode)
{ {
m_searchTimeout->stop(); m_searchTimeout->stop();
const auto errMsg = QString::fromUtf8(m_searchProcess->readAllStandardError()).trimmed();
if (!errMsg.isEmpty())
{
qWarning("%s", qUtf8Printable(errMsg));
LogMsg(tr("Error occurred in search engine. Search query: \"%1\". Category: \"%2\". Engines: \"%3\". Error: \"%4\".")
.arg(m_pattern, m_category, m_usedPlugins.join(u", "), errMsg), Log::WARNING);
}
if (m_searchCancelled) if (m_searchCancelled)
emit searchFinished(true); emit searchFinished(true);
else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0)) else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0))
emit searchFinished(false); emit searchFinished(false);
else else
emit searchFailed(); emit searchFailed(errMsg);
} }
// search QProcess return output as soon as it gets new // search QProcess return output as soon as it gets new
@@ -161,12 +200,6 @@ void SearchHandler::readSearchOutput()
} }
} }
void SearchHandler::processFailed()
{
if (!m_searchCancelled)
emit searchFailed();
}
// Parse one line of search results list // Parse one line of search results list
// Line is in the following form: // Line is in the following form:
// file url | file name | file size | nb seeds | nb leechers | Search engine url // file url | file name | file size | nb seeds | nb leechers | Search engine url

View File

@@ -74,12 +74,11 @@ public:
signals: signals:
void searchFinished(bool cancelled = false); void searchFinished(bool cancelled = false);
void searchFailed(); void searchFailed(const QString &errorMessage);
void newSearchResults(const QList<SearchResult> &results); void newSearchResults(const QList<SearchResult> &results);
private: private:
void readSearchOutput(); void readSearchOutput();
void processFailed();
void processFinished(int exitcode); void processFinished(int exitcode);
bool parseSearchResult(QByteArrayView line, SearchResult &searchResult); bool parseSearchResult(QByteArrayView line, SearchResult &searchResult);

View File

@@ -31,6 +31,7 @@
#include <memory> #include <memory>
#include <QtLogging>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QDomDocument> #include <QDomDocument>
@@ -559,6 +560,13 @@ void SearchPluginManager::update()
nova.start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly); nova.start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly);
nova.waitForFinished(); nova.waitForFinished();
if (const auto errMsg = QString::fromUtf8(nova.readAllStandardError()).trimmed()
; !errMsg.isEmpty())
{
qWarning("%s", qUtf8Printable(errMsg));
LogMsg(tr("Error occurred when fetching search engine capabilities. Error: \"%1\".").arg(errMsg), Log::WARNING);
}
const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput()); const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput());
QDomDocument xmlDoc; QDomDocument xmlDoc;
if (!xmlDoc.setContent(capabilities)) if (!xmlDoc.setContent(capabilities))

View File

@@ -64,10 +64,12 @@ namespace
return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText); return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText);
} }
QString statusText(SearchJobWidget::Status st) QString statusText(const SearchJobWidget::Status st)
{ {
switch (st) switch (st)
{ {
case SearchJobWidget::Status::Ready:
break;
case SearchJobWidget::Status::Ongoing: case SearchJobWidget::Status::Ongoing:
return SearchJobWidget::tr("Searching..."); return SearchJobWidget::tr("Searching...");
case SearchJobWidget::Status::Finished: case SearchJobWidget::Status::Finished:
@@ -78,9 +80,8 @@ namespace
return SearchJobWidget::tr("An error occurred during search..."); return SearchJobWidget::tr("An error occurred during search...");
case SearchJobWidget::Status::NoResults: case SearchJobWidget::Status::NoResults:
return SearchJobWidget::tr("Search returned no results"); return SearchJobWidget::tr("Search returned no results");
default:
return {};
} }
return {};
} }
} }
@@ -402,7 +403,7 @@ void SearchJobWidget::copyField(const int column) const
QApplication::clipboard()->setText(list.join(u'\n')); QApplication::clipboard()->setText(list.join(u'\n'));
} }
void SearchJobWidget::setStatus(Status value) void SearchJobWidget::setStatus(const Status value)
{ {
if (m_status == value) if (m_status == value)
return; return;
@@ -426,9 +427,12 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
else else
{ {
SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(engineName, torrentUrl); SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(engineName, torrentUrl);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished connect(downloadHandler, &SearchDownloadHandler::downloadFinished, this
, this, [this, option](const QString &source) { addTorrentToSession(source, option); }); , [this, downloadHandler, option](const QString &source, [[maybe_unused]] const QString &errorMessage)
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater); {
addTorrentToSession(source, option);
downloadHandler->deleteLater();
});
} }
setRowVisited(rowIndex.row()); setRowVisited(rowIndex.row());
@@ -631,7 +635,7 @@ void SearchJobWidget::searchFinished(bool cancelled)
setStatus(Status::Finished); setStatus(Status::Finished);
} }
void SearchJobWidget::searchFailed() void SearchJobWidget::searchFailed([[maybe_unused]] const QString &errorMessage)
{ {
setStatus(Status::Error); setStatus(Status::Error);
} }

View File

@@ -112,7 +112,7 @@ private:
void contextMenuEvent(QContextMenuEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override;
void onItemDoubleClicked(const QModelIndex &index); void onItemDoubleClicked(const QModelIndex &index);
void searchFinished(bool cancelled); void searchFinished(bool cancelled);
void searchFailed(); void searchFailed(const QString &errorMessage);
void appendSearchResults(const QList<SearchResult> &results); void appendSearchResults(const QList<SearchResult> &results);
void updateResultsCount(); void updateResultsCount();
void setStatus(Status value); void setStatus(Status value);

View File

@@ -114,8 +114,8 @@ void SearchController::startAction()
const auto id = generateSearchId(); const auto id = generateSearchId();
const std::shared_ptr<SearchHandler> searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)}; const std::shared_ptr<SearchHandler> searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)};
QObject::connect(searchHandler.get(), &SearchHandler::searchFinished, this, [id, this]() { m_activeSearches.remove(id); }); connect(searchHandler.get(), &SearchHandler::searchFinished, this, [this, id] { m_activeSearches.remove(id); });
QObject::connect(searchHandler.get(), &SearchHandler::searchFailed, this, [id, this]() { m_activeSearches.remove(id); }); connect(searchHandler.get(), &SearchHandler::searchFailed, this, [this, id]([[maybe_unused]] const QString &errorMessage) { m_activeSearches.remove(id); });
m_searchHandlers.insert(id, searchHandler); m_searchHandlers.insert(id, searchHandler);
@@ -235,8 +235,8 @@ void SearchController::downloadTorrentAction()
else else
{ {
SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(pluginName, torrentUrl); SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(pluginName, torrentUrl);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished connect(downloadHandler, &SearchDownloadHandler::downloadFinished, this
, this, [this, downloadHandler](const QString &source) , [this, downloadHandler](const QString &source, [[maybe_unused]] const QString &errorMessage)
{ {
app()->addTorrentManager()->addTorrent(source); app()->addTorrentManager()->addTorrent(source);
downloadHandler->deleteLater(); downloadHandler->deleteLater();