PluginInstallWizard: Reuse TaskTree

Change-Id: I0e139627a9d4c3401bcf7727ef1e51b2de858b77
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2023-06-04 20:30:40 +02:00
parent f88db30319
commit 3e074a52c1

View File

@@ -28,7 +28,6 @@
#include <QButtonGroup>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
@@ -38,6 +37,7 @@
#include <memory>
using namespace ExtensionSystem;
using namespace Tasking;
using namespace Utils;
struct Data
@@ -117,12 +117,11 @@ public:
if (hasLibSuffix(path))
return true;
QString error;
if (!Archive::supportsFile(path, &error)) {
m_info->setText(error);
return false;
}
return true;
const auto sourceAndCommand = Unarchiver::sourceAndCommand(path);
if (!sourceAndCommand)
m_info->setText(sourceAndCommand.error());
return bool(sourceAndCommand);
}
int nextId() const final
@@ -136,15 +135,48 @@ public:
Data *m_data = nullptr;
};
struct ArchiveIssue
{
QString message;
InfoLabel::InfoType type;
};
// Async. Result is set if any issue was found.
void checkContents(QPromise<ArchiveIssue> &promise, const FilePath &tempDir)
{
PluginSpec *coreplugin = PluginManager::specForPlugin(CorePlugin::instance());
// look for plugin
QDirIterator it(tempDir.path(), libraryNameFilter(), QDir::Files | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (it.hasNext()) {
if (promise.isCanceled())
return;
it.next();
PluginSpec *spec = PluginSpec::read(it.filePath());
if (spec) {
// Is a Qt Creator plugin. Let's see if we find a Core dependency and check the
// version
const QVector<PluginDependency> dependencies = spec->dependencies();
const auto found = std::find_if(dependencies.constBegin(), dependencies.constEnd(),
[coreplugin](const PluginDependency &d) { return d.name == coreplugin->name(); });
if (found == dependencies.constEnd())
return;
if (coreplugin->provides(found->name, found->version))
return;
promise.addResult(ArchiveIssue{
Tr::tr("Plugin requires an incompatible version of %1 (%2).")
.arg(Constants::IDE_DISPLAY_NAME, found->version), InfoLabel::Error});
return; // successful / no error
}
}
promise.addResult(ArchiveIssue{Tr::tr("Did not find %1 plugin.")
.arg(Constants::IDE_DISPLAY_NAME), InfoLabel::Error});
}
class CheckArchivePage : public WizardPage
{
public:
struct ArchiveIssue
{
QString message;
InfoLabel::InfoType type;
};
CheckArchivePage(Data *data, QWidget *parent)
: WizardPage(parent)
, m_data(data)
@@ -155,6 +187,12 @@ public:
m_label->setElideMode(Qt::ElideNone);
m_label->setWordWrap(true);
m_cancelButton = new QPushButton(Tr::tr("Cancel"));
connect(m_cancelButton, &QPushButton::clicked, this, [this] {
m_taskTree.reset();
m_cancelButton->setVisible(false);
m_label->setType(InfoLabel::Information);
m_label->setText(Tr::tr("Canceled."));
});
m_output = new QTextEdit;
m_output->setReadOnly(true);
@@ -169,142 +207,87 @@ public:
{
m_isComplete = false;
emit completeChanged();
m_canceled = false;
m_tempDir = std::make_unique<TemporaryDirectory>("plugininstall");
m_data->extractedPath = m_tempDir->path();
m_label->setText(Tr::tr("Checking archive..."));
m_label->setType(InfoLabel::None);
m_cancelButton->setVisible(true);
m_output->clear();
m_archive.reset(new Archive(m_data->sourcePath, m_tempDir->path()));
if (!m_archive->isValid()) {
const auto sourceAndCommand = Unarchiver::sourceAndCommand(m_data->sourcePath);
if (!sourceAndCommand) {
m_label->setType(InfoLabel::Error);
m_label->setText(Tr::tr("The file is not an archive."));
m_label->setText(sourceAndCommand.error());
return;
}
QObject::connect(m_archive.get(), &Archive::outputReceived, this,
[this](const QString &output) {
m_output->append(output);
});
QObject::connect(m_archive.get(), &Archive::finished, this, [this](bool success) {
m_archive.release()->deleteLater();
handleFinished(success);
});
QObject::connect(m_cancelButton, &QPushButton::clicked, this, [this] {
m_canceled = true;
m_archive.reset();
handleFinished(false);
});
m_archive->unarchive();
}
void handleFinished(bool success)
{
m_cancelButton->disconnect();
if (!success) { // unarchiving failed
m_cancelButton->setVisible(false);
if (m_canceled) {
m_label->setType(InfoLabel::Information);
m_label->setText(Tr::tr("Canceled."));
const auto onUnarchiverSetup = [this, sourceAndCommand](Unarchiver &unarchiver) {
unarchiver.setSourceAndCommand(*sourceAndCommand);
unarchiver.setDestDir(m_tempDir->path());
connect(&unarchiver, &Unarchiver::outputReceived, this, [this](const QString &output) {
m_output->append(output);
});
};
const auto onUnarchiverError = [this](const Unarchiver &) {
m_label->setType(InfoLabel::Error);
m_label->setText(Tr::tr("There was an error while unarchiving."));
};
const auto onCheckerSetup = [this](Async<ArchiveIssue> &async) {
if (!m_tempDir)
return TaskAction::StopWithError;
async.setConcurrentCallData(checkContents, m_tempDir->path());
async.setFutureSynchronizer(PluginManager::futureSynchronizer());
return TaskAction::Continue;
};
const auto onCheckerDone = [this](const Async<ArchiveIssue> &async) {
m_isComplete = !async.isResultAvailable();
if (m_isComplete) {
m_label->setType(InfoLabel::Ok);
m_label->setText(Tr::tr("Archive is OK."));
} else {
m_label->setType(InfoLabel::Error);
m_label->setText(Tr::tr("There was an error while unarchiving."));
const ArchiveIssue issue = async.result();
m_label->setType(issue.type);
m_label->setText(issue.message);
}
} else { // unarchiving was successful, run a check
m_archiveCheck = Utils::asyncRun([this](QPromise<ArchiveIssue> &promise)
{ return checkContents(promise); });
Utils::onFinished(m_archiveCheck, this, [this](const QFuture<ArchiveIssue> &f) {
m_cancelButton->setVisible(false);
m_cancelButton->disconnect();
const bool ok = f.resultCount() == 0 && !f.isCanceled();
if (f.isCanceled()) {
m_label->setType(InfoLabel::Information);
m_label->setText(Tr::tr("Canceled."));
} else if (ok) {
m_label->setType(InfoLabel::Ok);
m_label->setText(Tr::tr("Archive is OK."));
} else {
const ArchiveIssue issue = f.result();
m_label->setType(issue.type);
m_label->setText(issue.message);
}
m_isComplete = ok;
emit completeChanged();
});
QObject::connect(m_cancelButton, &QPushButton::clicked, this, [this] {
m_archiveCheck.cancel();
});
}
}
emit completeChanged();
};
// Async. Result is set if any issue was found.
void checkContents(QPromise<ArchiveIssue> &promise)
{
QTC_ASSERT(m_tempDir.get(), return );
const Group root {
UnarchiverTask(onUnarchiverSetup, {}, onUnarchiverError),
AsyncTask<ArchiveIssue>(onCheckerSetup, onCheckerDone)
};
m_taskTree.reset(new TaskTree(root));
PluginSpec *coreplugin = PluginManager::specForPlugin(CorePlugin::instance());
const auto onEnd = [this] {
m_cancelButton->setVisible(false);
m_taskTree.release()->deleteLater();
};
connect(m_taskTree.get(), &TaskTree::done, this, onEnd);
connect(m_taskTree.get(), &TaskTree::errorOccurred, this, onEnd);
// look for plugin
QDirIterator it(m_tempDir->path().path(),
libraryNameFilter(),
QDir::Files | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (it.hasNext()) {
if (promise.isCanceled())
return;
it.next();
PluginSpec *spec = PluginSpec::read(it.filePath());
if (spec) {
// Is a Qt Creator plugin. Let's see if we find a Core dependency and check the
// version
const QVector<PluginDependency> dependencies = spec->dependencies();
const auto found = std::find_if(dependencies.constBegin(),
dependencies.constEnd(),
[coreplugin](const PluginDependency &d) {
return d.name == coreplugin->name();
});
if (found != dependencies.constEnd()) {
if (!coreplugin->provides(found->name, found->version)) {
promise.addResult(ArchiveIssue{
Tr::tr("Plugin requires an incompatible version of %1 (%2).")
.arg(Constants::IDE_DISPLAY_NAME).arg(found->version),
InfoLabel::Error});
return;
}
}
return; // successful / no error
}
}
promise.addResult(ArchiveIssue{Tr::tr("Did not find %1 plugin.")
.arg(Constants::IDE_DISPLAY_NAME), InfoLabel::Error});
m_cancelButton->setVisible(true);
m_taskTree->start();
}
void cleanupPage() final
{
// back button pressed
m_cancelButton->disconnect();
m_archive.reset();
if (m_archiveCheck.isRunning()) {
m_archiveCheck.cancel();
m_archiveCheck.waitForFinished();
}
m_taskTree.reset();
m_tempDir.reset();
}
bool isComplete() const final { return m_isComplete; }
std::unique_ptr<TemporaryDirectory> m_tempDir;
std::unique_ptr<Archive> m_archive;
QFuture<ArchiveIssue> m_archiveCheck;
std::unique_ptr<TaskTree> m_taskTree;
InfoLabel *m_label = nullptr;
QPushButton *m_cancelButton = nullptr;
QTextEdit *m_output = nullptr;
Data *m_data = nullptr;
bool m_isComplete = false;
bool m_canceled = false;
};
class InstallLocationPage : public WizardPage