From 10b879bdafa41bda2d41820506623a9d4d30bc08 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 27 Sep 2025 15:42:51 +0800 Subject: [PATCH] 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. --- src/base/search/searchdownloadhandler.cpp | 17 ++++++-- src/base/search/searchdownloadhandler.h | 5 ++- src/base/search/searchhandler.cpp | 49 +++++++++++++++++++---- src/base/search/searchhandler.h | 3 +- src/base/search/searchpluginmanager.cpp | 8 ++++ src/gui/search/searchjobwidget.cpp | 20 +++++---- src/gui/search/searchjobwidget.h | 2 +- src/webui/api/searchcontroller.cpp | 8 ++-- 8 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/base/search/searchdownloadhandler.cpp b/src/base/search/searchdownloadhandler.cpp index 42869579f..27823d9b9 100644 --- a/src/base/search/searchdownloadhandler.cpp +++ b/src/base/search/searchdownloadhandler.cpp @@ -28,9 +28,11 @@ #include "searchdownloadhandler.h" +#include #include #include "base/global.h" +#include "base/logger.h" #include "base/path.h" #include "base/utils/foreignapps.h" #include "base/utils/fs.h" @@ -38,6 +40,8 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager) : QObject(manager) + , m_pluginName {pluginName} + , m_url {url} , m_manager {manager} , 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); } -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)) { const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed(); @@ -70,5 +81,5 @@ void SearchDownloadHandler::downloadProcessFinished(int exitcode) path = parts[0].toString(); } - emit downloadFinished(path); + emit downloadFinished(path, errMsg); } diff --git a/src/base/search/searchdownloadhandler.h b/src/base/search/searchdownloadhandler.h index 426560e0a..9dd9e58db 100644 --- a/src/base/search/searchdownloadhandler.h +++ b/src/base/search/searchdownloadhandler.h @@ -29,6 +29,7 @@ #pragma once #include +#include class QProcess; @@ -44,11 +45,13 @@ class SearchDownloadHandler : public QObject SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager); signals: - void downloadFinished(const QString &path); + void downloadFinished(const QString &path, const QString &errorMessage); private: void downloadProcessFinished(int exitcode); + QString m_pluginName; + QString m_url; SearchPluginManager *m_manager = nullptr; QProcess *m_downloadProcess = nullptr; }; diff --git a/src/base/search/searchhandler.cpp b/src/base/search/searchhandler.cpp index d1d9fb4b7..670c2b42a 100644 --- a/src/base/search/searchhandler.cpp +++ b/src/base/search/searchhandler.cpp @@ -31,12 +31,14 @@ #include +#include #include #include #include #include #include "base/global.h" +#include "base/logger.h" #include "base/path.h" #include "base/utils/bytearray.h" #include "base/utils/foreignapps.h" @@ -59,6 +61,26 @@ namespace PL_PUB_DATE, 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) @@ -86,7 +108,16 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co }; 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, qOverload(&QProcess::finished) , this, &SearchHandler::processFinished); @@ -127,12 +158,20 @@ void SearchHandler::processFinished(const int exitcode) { 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) emit searchFinished(true); else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0)) emit searchFinished(false); else - emit searchFailed(); + emit searchFailed(errMsg); } // 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 // Line is in the following form: // file url | file name | file size | nb seeds | nb leechers | Search engine url diff --git a/src/base/search/searchhandler.h b/src/base/search/searchhandler.h index fe6fe9d76..7af9f928e 100644 --- a/src/base/search/searchhandler.h +++ b/src/base/search/searchhandler.h @@ -74,12 +74,11 @@ public: signals: void searchFinished(bool cancelled = false); - void searchFailed(); + void searchFailed(const QString &errorMessage); void newSearchResults(const QList &results); private: void readSearchOutput(); - void processFailed(); void processFinished(int exitcode); bool parseSearchResult(QByteArrayView line, SearchResult &searchResult); diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index 8fc7de188..bd4b14352 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -31,6 +31,7 @@ #include +#include #include #include #include @@ -559,6 +560,13 @@ void SearchPluginManager::update() nova.start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly); 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()); QDomDocument xmlDoc; if (!xmlDoc.setContent(capabilities)) diff --git a/src/gui/search/searchjobwidget.cpp b/src/gui/search/searchjobwidget.cpp index 5544ea150..c71ec7b2f 100644 --- a/src/gui/search/searchjobwidget.cpp +++ b/src/gui/search/searchjobwidget.cpp @@ -64,10 +64,12 @@ namespace return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText); } - QString statusText(SearchJobWidget::Status st) + QString statusText(const SearchJobWidget::Status st) { switch (st) { + case SearchJobWidget::Status::Ready: + break; case SearchJobWidget::Status::Ongoing: return SearchJobWidget::tr("Searching..."); case SearchJobWidget::Status::Finished: @@ -78,9 +80,8 @@ namespace return SearchJobWidget::tr("An error occurred during search..."); case SearchJobWidget::Status::NoResults: 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')); } -void SearchJobWidget::setStatus(Status value) +void SearchJobWidget::setStatus(const Status value) { if (m_status == value) return; @@ -426,9 +427,12 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr else { SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(engineName, torrentUrl); - connect(downloadHandler, &SearchDownloadHandler::downloadFinished - , this, [this, option](const QString &source) { addTorrentToSession(source, option); }); - connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater); + connect(downloadHandler, &SearchDownloadHandler::downloadFinished, this + , [this, downloadHandler, option](const QString &source, [[maybe_unused]] const QString &errorMessage) + { + addTorrentToSession(source, option); + downloadHandler->deleteLater(); + }); } setRowVisited(rowIndex.row()); @@ -631,7 +635,7 @@ void SearchJobWidget::searchFinished(bool cancelled) setStatus(Status::Finished); } -void SearchJobWidget::searchFailed() +void SearchJobWidget::searchFailed([[maybe_unused]] const QString &errorMessage) { setStatus(Status::Error); } diff --git a/src/gui/search/searchjobwidget.h b/src/gui/search/searchjobwidget.h index f6b879fce..f2842f470 100644 --- a/src/gui/search/searchjobwidget.h +++ b/src/gui/search/searchjobwidget.h @@ -112,7 +112,7 @@ private: void contextMenuEvent(QContextMenuEvent *event) override; void onItemDoubleClicked(const QModelIndex &index); void searchFinished(bool cancelled); - void searchFailed(); + void searchFailed(const QString &errorMessage); void appendSearchResults(const QList &results); void updateResultsCount(); void setStatus(Status value); diff --git a/src/webui/api/searchcontroller.cpp b/src/webui/api/searchcontroller.cpp index c8c5298f2..0257b16ec 100644 --- a/src/webui/api/searchcontroller.cpp +++ b/src/webui/api/searchcontroller.cpp @@ -114,8 +114,8 @@ void SearchController::startAction() const auto id = generateSearchId(); const std::shared_ptr searchHandler {SearchPluginManager::instance()->startSearch(pattern, category, pluginsToUse)}; - QObject::connect(searchHandler.get(), &SearchHandler::searchFinished, this, [id, this]() { m_activeSearches.remove(id); }); - QObject::connect(searchHandler.get(), &SearchHandler::searchFailed, this, [id, this]() { m_activeSearches.remove(id); }); + connect(searchHandler.get(), &SearchHandler::searchFinished, this, [this, id] { 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); @@ -235,8 +235,8 @@ void SearchController::downloadTorrentAction() else { SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(pluginName, torrentUrl); - connect(downloadHandler, &SearchDownloadHandler::downloadFinished - , this, [this, downloadHandler](const QString &source) + connect(downloadHandler, &SearchDownloadHandler::downloadFinished, this + , [this, downloadHandler](const QString &source, [[maybe_unused]] const QString &errorMessage) { app()->addTorrentManager()->addTorrent(source); downloadHandler->deleteLater();