Utils: Add async variant of DataFromProcess

... and make use of it in PathChooser.

Change-Id: I0e81afec2caf38f488a8ab98b55016535c187fc2
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Christian Kandeler
2024-02-19 15:46:46 +01:00
parent 15fc626a78
commit 4a722bfa9d
4 changed files with 108 additions and 52 deletions

View File

@@ -16,6 +16,7 @@
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <memory>
#include <optional> #include <optional>
#include <utility> #include <utility>
@@ -32,6 +33,7 @@ public:
public: public:
using OutputParser = std::function<std::optional<Data>(const QString &)>; using OutputParser = std::function<std::optional<Data>(const QString &)>;
using ErrorHandler = std::function<void(const Process &)>; using ErrorHandler = std::function<void(const Process &)>;
using Callback = std::function<void(const std::optional<Data> &)>;
Parameters(const CommandLine &cmdLine, const OutputParser &parser) Parameters(const CommandLine &cmdLine, const OutputParser &parser)
: commandLine(cmdLine) : commandLine(cmdLine)
@@ -43,25 +45,50 @@ public:
std::chrono::seconds timeout = std::chrono::seconds(10); std::chrono::seconds timeout = std::chrono::seconds(10);
OutputParser parser; OutputParser parser;
ErrorHandler errorHandler; ErrorHandler errorHandler;
Callback callback;
QList<ProcessResult> allowedResults{ProcessResult::FinishedWithSuccess}; QList<ProcessResult> allowedResults{ProcessResult::FinishedWithSuccess};
}; };
// Use the first variant whenever possible.
static void provideData(const Parameters &params);
static std::optional<Data> getData(const Parameters &params); static std::optional<Data> getData(const Parameters &params);
// TODO: async variant.
private: private:
using Key = std::tuple<FilePath, QStringList, QString>; using Key = std::tuple<FilePath, QStringList, QString>;
using Value = std::pair<std::optional<Data>, QDateTime>; using Value = std::pair<std::optional<Data>, QDateTime>;
static std::optional<Data> getOrProvideData(const Parameters &params);
static std::optional<Data> handleProcessFinished(const Parameters &params,
const QDateTime &exeTimestamp,
const Key &cacheKey,
const std::shared_ptr<Process> &process);
static inline QHash<Key, Value> m_cache; static inline QHash<Key, Value> m_cache;
static inline QMutex m_cacheMutex; static inline QMutex m_cacheMutex;
}; };
template<typename Data>
inline void DataFromProcess<Data>::provideData(const Parameters &params)
{
QTC_ASSERT(params.callback, return);
getOrProvideData(params);
}
template<typename Data> template<typename Data>
inline std::optional<Data> DataFromProcess<Data>::getData(const Parameters &params) inline std::optional<Data> DataFromProcess<Data>::getData(const Parameters &params)
{ {
if (params.commandLine.executable().isEmpty()) QTC_ASSERT(!params.callback, return {});
return getOrProvideData(params);
}
template<typename Data>
inline std::optional<Data> DataFromProcess<Data>::getOrProvideData(const Parameters &params)
{
if (params.commandLine.executable().isEmpty()) {
if (params.callback)
params.callback({});
return {}; return {};
}
const auto key = std::make_tuple(params.commandLine.executable(), const auto key = std::make_tuple(params.commandLine.executable(),
params.environment.toStringList(), params.environment.toStringList(),
@@ -74,21 +101,47 @@ inline std::optional<Data> DataFromProcess<Data>::getData(const Parameters &para
return it.value().first; return it.value().first;
} }
Process outputRetriever; const auto outputRetriever = std::make_shared<Process>();
outputRetriever.setCommand(params.commandLine); outputRetriever->setCommand(params.commandLine);
outputRetriever.runBlocking(params.timeout); if (params.callback) {
QObject::connect(outputRetriever.get(),
// Do not store into cache: The next call might succeed. &Process::done,
if (outputRetriever.result() == ProcessResult::Canceled) [params, exeTimestamp, key, outputRetriever] {
handleProcessFinished(params, exeTimestamp, key, outputRetriever);
});
outputRetriever->start();
return {}; return {};
}
outputRetriever->runBlocking(params.timeout);
return handleProcessFinished(params, exeTimestamp, key, outputRetriever);
}
template<typename Data>
inline std::optional<Data> DataFromProcess<Data>::handleProcessFinished(
const Parameters &params,
const QDateTime &exeTimestamp,
const Key &cacheKey,
const std::shared_ptr<Process> &process)
{
// Do not store into cache: The next call might succeed.
if (process->result() == ProcessResult::Canceled) {
if (params.callback)
params.callback({});
return {};
}
std::optional<Data> data; std::optional<Data> data;
if (params.allowedResults.contains(outputRetriever.result())) if (params.allowedResults.contains(process->result()))
data = params.parser(outputRetriever.cleanedStdOut()); data = params.parser(process->cleanedStdOut());
else if (params.errorHandler) else if (params.errorHandler)
params.errorHandler(outputRetriever); params.errorHandler(*process);
QMutexLocker<QMutex> cacheLocker(&m_cacheMutex); QMutexLocker<QMutex> cacheLocker(&m_cacheMutex);
m_cache.insert(key, std::make_pair(data, exeTimestamp)); m_cache.insert(cacheKey, std::make_pair(data, exeTimestamp));
if (params.callback) {
params.callback(data);
return {};
}
return data; return data;
} }

View File

@@ -19,6 +19,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QFuture> #include <QFuture>
#include <QGuiApplication> #include <QGuiApplication>
#include <QHelpEvent>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMenu> #include <QMenu>
#include <QPushButton> #include <QPushButton>
@@ -85,8 +86,6 @@ public:
QStringList arguments() const { return m_arguments; } QStringList arguments() const { return m_arguments; }
void setArguments(const QStringList &arguments) { m_arguments = arguments; } void setArguments(const QStringList &arguments) { m_arguments = arguments; }
static QString toolVersion(const CommandLine &cmd);
private: private:
// Extension point for concatenating existing tooltips. // Extension point for concatenating existing tooltips.
virtual QString defaultToolTip() const { return QString(); } virtual QString defaultToolTip() const { return QString(); }
@@ -108,35 +107,41 @@ bool BinaryVersionToolTipEventFilter::eventFilter(QObject *o, QEvent *e)
QTC_ASSERT(le, return false); QTC_ASSERT(le, return false);
const QString binary = le->text(); const QString binary = le->text();
if (!binary.isEmpty()) { DataFromProcess<QString>::Parameters params(CommandLine(FilePath::fromString(
const QString version = BinaryVersionToolTipEventFilter::toolVersion( QDir::cleanPath(binary)),
CommandLine(FilePath::fromString(QDir::cleanPath(binary)), m_arguments)); m_arguments),
if (!version.isEmpty()) { [](const QString &output) { return output; });
// Concatenate tooltips. params.callback = [binary, self = QPointer(this),
QString tooltip = "<html><head/><body>"; le = QPointer(le)](const std::optional<QString> &version) {
const QString defaultValue = defaultToolTip(); if (!self || !le)
if (!defaultValue.isEmpty()) { return;
tooltip += "<p>"; if (!version || version->isEmpty())
tooltip += defaultValue; return;
tooltip += "</p>"; if (binary != le->text())
} return;
tooltip += "<pre>";
tooltip += version;
tooltip += "</pre><body></html>";
le->setToolTip(tooltip);
}
}
return false;
}
QString BinaryVersionToolTipEventFilter::toolVersion(const CommandLine &cmd) // Concatenate tooltips.
{ QString tooltip = "<html><head/><body>";
DataFromProcess<QString>::Parameters params(cmd, [](const QString &output) { return output; }); const QString defaultValue = self->defaultToolTip();
using namespace std::chrono_literals; if (!defaultValue.isEmpty()) {
params.timeout = 1s; tooltip += "<p>";
if (const auto version = DataFromProcess<QString>::getData(params)) tooltip += defaultValue;
return *version; tooltip += "</p>";
return {}; }
tooltip += "<pre>";
tooltip += *version;
tooltip += "</pre><body></html>";
le->setToolTip(tooltip);
if (QRect(le->mapToGlobal(QPoint(0, 0)), le->size()).contains(QCursor::pos())) {
QCoreApplication::postEvent(le,
new QHelpEvent(QEvent::ToolTip,
le->mapFromGlobal(QCursor::pos()),
QCursor::pos()));
}
};
DataFromProcess<QString>::provideData(params);
return false;
} }
// Extends BinaryVersionToolTipEventFilter to prepend the existing pathchooser // Extends BinaryVersionToolTipEventFilter to prepend the existing pathchooser
@@ -722,11 +727,6 @@ FancyLineEdit *PathChooser::lineEdit() const
return d->m_lineEdit; return d->m_lineEdit;
} }
QString PathChooser::toolVersion(const CommandLine &cmd)
{
return BinaryVersionToolTipEventFilter::toolVersion(cmd);
}
void PathChooser::installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments) void PathChooser::installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments)
{ {
auto ef = new BinaryVersionToolTipEventFilter(le); auto ef = new BinaryVersionToolTipEventFilter(le);

View File

@@ -99,8 +99,6 @@ public:
QStringList commandVersionArguments() const; QStringList commandVersionArguments() const;
void setCommandVersionArguments(const QStringList &arguments); void setCommandVersionArguments(const QStringList &arguments);
// Utility to run a tool and return its stdout.
static QString toolVersion(const CommandLine &cmd);
// Install a tooltip on lineedits used for binaries showing the version. // Install a tooltip on lineedits used for binaries showing the version.
static void installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments); static void installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments);

View File

@@ -5,6 +5,7 @@
#include "gerritplugin.h" #include "gerritplugin.h"
#include <utils/commandline.h> #include <utils/commandline.h>
#include <utils/datafromprocess.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
@@ -70,8 +71,12 @@ void GerritParameters::setPortFlagBySshType()
{ {
bool isPlink = false; bool isPlink = false;
if (!ssh.isEmpty()) { if (!ssh.isEmpty()) {
const QString version = PathChooser::toolVersion({ssh, {"-V"}}); DataFromProcess<QString>::Parameters params({ssh, {"-V"}},
isPlink = version.contains("plink", Qt::CaseInsensitive); [](const QString &output) { return output; });
using namespace std::chrono_literals;
params.timeout = 1s;
if (const auto version = DataFromProcess<QString>::getData(params))
isPlink = version->contains("plink", Qt::CaseInsensitive);
} }
portFlag = QLatin1String(isPlink ? "-P" : defaultPortFlag); portFlag = QLatin1String(isPlink ? "-P" : defaultPortFlag);
} }