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 <QtLogging>
#include <QProcess>
#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);
}

View File

@@ -29,6 +29,7 @@
#pragma once
#include <QObject>
#include <QString>
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;
};

View File

@@ -31,12 +31,14 @@
#include <chrono>
#include <QtLogging>
#include <QList>
#include <QMetaObject>
#include <QProcess>
#include <QTimer>
#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<int, QProcess::ExitStatus>(&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

View File

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

View File

@@ -31,6 +31,7 @@
#include <memory>
#include <QtLogging>
#include <QDir>
#include <QDirIterator>
#include <QDomDocument>
@@ -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))

View File

@@ -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);
}

View File

@@ -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<SearchResult> &results);
void updateResultsCount();
void setStatus(Status value);

View File

@@ -114,8 +114,8 @@ void SearchController::startAction()
const auto id = generateSearchId();
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); });
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();