forked from qt-creator/qt-creator
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:
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
@@ -32,6 +33,7 @@ public:
|
||||
public:
|
||||
using OutputParser = std::function<std::optional<Data>(const QString &)>;
|
||||
using ErrorHandler = std::function<void(const Process &)>;
|
||||
using Callback = std::function<void(const std::optional<Data> &)>;
|
||||
|
||||
Parameters(const CommandLine &cmdLine, const OutputParser &parser)
|
||||
: commandLine(cmdLine)
|
||||
@@ -43,25 +45,50 @@ public:
|
||||
std::chrono::seconds timeout = std::chrono::seconds(10);
|
||||
OutputParser parser;
|
||||
ErrorHandler errorHandler;
|
||||
Callback callback;
|
||||
QList<ProcessResult> allowedResults{ProcessResult::FinishedWithSuccess};
|
||||
};
|
||||
|
||||
// Use the first variant whenever possible.
|
||||
static void provideData(const Parameters ¶ms);
|
||||
static std::optional<Data> getData(const Parameters ¶ms);
|
||||
|
||||
// TODO: async variant.
|
||||
|
||||
private:
|
||||
using Key = std::tuple<FilePath, QStringList, QString>;
|
||||
using Value = std::pair<std::optional<Data>, QDateTime>;
|
||||
|
||||
static std::optional<Data> getOrProvideData(const Parameters ¶ms);
|
||||
static std::optional<Data> handleProcessFinished(const Parameters ¶ms,
|
||||
const QDateTime &exeTimestamp,
|
||||
const Key &cacheKey,
|
||||
const std::shared_ptr<Process> &process);
|
||||
|
||||
static inline QHash<Key, Value> m_cache;
|
||||
static inline QMutex m_cacheMutex;
|
||||
};
|
||||
|
||||
template<typename Data>
|
||||
inline void DataFromProcess<Data>::provideData(const Parameters ¶ms)
|
||||
{
|
||||
QTC_ASSERT(params.callback, return);
|
||||
getOrProvideData(params);
|
||||
}
|
||||
|
||||
template<typename Data>
|
||||
inline std::optional<Data> DataFromProcess<Data>::getData(const Parameters ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
if (params.commandLine.executable().isEmpty()) {
|
||||
if (params.callback)
|
||||
params.callback({});
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto key = std::make_tuple(params.commandLine.executable(),
|
||||
params.environment.toStringList(),
|
||||
@@ -74,21 +101,47 @@ inline std::optional<Data> DataFromProcess<Data>::getData(const Parameters ¶
|
||||
return it.value().first;
|
||||
}
|
||||
|
||||
Process outputRetriever;
|
||||
outputRetriever.setCommand(params.commandLine);
|
||||
outputRetriever.runBlocking(params.timeout);
|
||||
|
||||
// Do not store into cache: The next call might succeed.
|
||||
if (outputRetriever.result() == ProcessResult::Canceled)
|
||||
const auto outputRetriever = std::make_shared<Process>();
|
||||
outputRetriever->setCommand(params.commandLine);
|
||||
if (params.callback) {
|
||||
QObject::connect(outputRetriever.get(),
|
||||
&Process::done,
|
||||
[params, exeTimestamp, key, outputRetriever] {
|
||||
handleProcessFinished(params, exeTimestamp, key, outputRetriever);
|
||||
});
|
||||
outputRetriever->start();
|
||||
return {};
|
||||
}
|
||||
|
||||
outputRetriever->runBlocking(params.timeout);
|
||||
return handleProcessFinished(params, exeTimestamp, key, outputRetriever);
|
||||
}
|
||||
|
||||
template<typename Data>
|
||||
inline std::optional<Data> DataFromProcess<Data>::handleProcessFinished(
|
||||
const Parameters ¶ms,
|
||||
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;
|
||||
if (params.allowedResults.contains(outputRetriever.result()))
|
||||
data = params.parser(outputRetriever.cleanedStdOut());
|
||||
if (params.allowedResults.contains(process->result()))
|
||||
data = params.parser(process->cleanedStdOut());
|
||||
else if (params.errorHandler)
|
||||
params.errorHandler(outputRetriever);
|
||||
params.errorHandler(*process);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QFuture>
|
||||
#include <QGuiApplication>
|
||||
#include <QHelpEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
@@ -85,8 +86,6 @@ public:
|
||||
QStringList arguments() const { return m_arguments; }
|
||||
void setArguments(const QStringList &arguments) { m_arguments = arguments; }
|
||||
|
||||
static QString toolVersion(const CommandLine &cmd);
|
||||
|
||||
private:
|
||||
// Extension point for concatenating existing tooltips.
|
||||
virtual QString defaultToolTip() const { return QString(); }
|
||||
@@ -108,35 +107,41 @@ bool BinaryVersionToolTipEventFilter::eventFilter(QObject *o, QEvent *e)
|
||||
QTC_ASSERT(le, return false);
|
||||
|
||||
const QString binary = le->text();
|
||||
if (!binary.isEmpty()) {
|
||||
const QString version = BinaryVersionToolTipEventFilter::toolVersion(
|
||||
CommandLine(FilePath::fromString(QDir::cleanPath(binary)), m_arguments));
|
||||
if (!version.isEmpty()) {
|
||||
DataFromProcess<QString>::Parameters params(CommandLine(FilePath::fromString(
|
||||
QDir::cleanPath(binary)),
|
||||
m_arguments),
|
||||
[](const QString &output) { return output; });
|
||||
params.callback = [binary, self = QPointer(this),
|
||||
le = QPointer(le)](const std::optional<QString> &version) {
|
||||
if (!self || !le)
|
||||
return;
|
||||
if (!version || version->isEmpty())
|
||||
return;
|
||||
if (binary != le->text())
|
||||
return;
|
||||
|
||||
// Concatenate tooltips.
|
||||
QString tooltip = "<html><head/><body>";
|
||||
const QString defaultValue = defaultToolTip();
|
||||
const QString defaultValue = self->defaultToolTip();
|
||||
if (!defaultValue.isEmpty()) {
|
||||
tooltip += "<p>";
|
||||
tooltip += defaultValue;
|
||||
tooltip += "</p>";
|
||||
}
|
||||
tooltip += "<pre>";
|
||||
tooltip += version;
|
||||
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()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
DataFromProcess<QString>::provideData(params);
|
||||
|
||||
QString BinaryVersionToolTipEventFilter::toolVersion(const CommandLine &cmd)
|
||||
{
|
||||
DataFromProcess<QString>::Parameters params(cmd, [](const QString &output) { return output; });
|
||||
using namespace std::chrono_literals;
|
||||
params.timeout = 1s;
|
||||
if (const auto version = DataFromProcess<QString>::getData(params))
|
||||
return *version;
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extends BinaryVersionToolTipEventFilter to prepend the existing pathchooser
|
||||
@@ -722,11 +727,6 @@ FancyLineEdit *PathChooser::lineEdit() const
|
||||
return d->m_lineEdit;
|
||||
}
|
||||
|
||||
QString PathChooser::toolVersion(const CommandLine &cmd)
|
||||
{
|
||||
return BinaryVersionToolTipEventFilter::toolVersion(cmd);
|
||||
}
|
||||
|
||||
void PathChooser::installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments)
|
||||
{
|
||||
auto ef = new BinaryVersionToolTipEventFilter(le);
|
||||
|
||||
@@ -99,8 +99,6 @@ public:
|
||||
QStringList commandVersionArguments() const;
|
||||
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.
|
||||
static void installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "gerritplugin.h"
|
||||
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/datafromprocess.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/pathchooser.h>
|
||||
@@ -70,8 +71,12 @@ void GerritParameters::setPortFlagBySshType()
|
||||
{
|
||||
bool isPlink = false;
|
||||
if (!ssh.isEmpty()) {
|
||||
const QString version = PathChooser::toolVersion({ssh, {"-V"}});
|
||||
isPlink = version.contains("plink", Qt::CaseInsensitive);
|
||||
DataFromProcess<QString>::Parameters params({ssh, {"-V"}},
|
||||
[](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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user