Plugin Wizard: Investigate archive before installing

So far that only moves unarchiving and the check if that succeeds to
within the wizard.
To be extended with additional checks for the archive content's sanity.

Change-Id: I6798937826fbe9cb584d83a920e67b6cfcc119ed
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Eike Ziller
2020-06-08 17:03:38 +02:00
parent 0eefc5b70d
commit 30b6bbfb6b

View File

@@ -32,6 +32,7 @@
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/infolabel.h> #include <utils/infolabel.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/temporarydirectory.h>
#include <utils/wizard.h> #include <utils/wizard.h>
#include <utils/wizardpage.h> #include <utils/wizardpage.h>
@@ -44,12 +45,19 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QRadioButton> #include <QRadioButton>
#include <QTextEdit>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <memory>
using namespace Utils; using namespace Utils;
const char kPath[] = "Path"; struct Data
const char kApplicationInstall[] = "ApplicationInstall"; {
FilePath sourcePath;
FilePath extractedPath;
bool installIntoApplication;
};
static bool hasLibSuffix(const FilePath &path) static bool hasLibSuffix(const FilePath &path)
{ {
@@ -58,14 +66,21 @@ static bool hasLibSuffix(const FilePath &path)
|| (HostOsInfo().isMacHost() && path.endsWith(".dylib")); || (HostOsInfo().isMacHost() && path.endsWith(".dylib"));
} }
static FilePath pluginInstallPath(bool installIntoApplication)
{
return FilePath::fromString(installIntoApplication ? Core::ICore::pluginPath()
: Core::ICore::userPluginPath());
}
namespace Core { namespace Core {
namespace Internal { namespace Internal {
class SourcePage : public WizardPage class SourcePage : public WizardPage
{ {
public: public:
SourcePage(QWidget *parent) SourcePage(Data *data, QWidget *parent)
: WizardPage(parent) : WizardPage(parent)
, m_data(data)
{ {
setTitle(PluginInstallWizard::tr("Source")); setTitle(PluginInstallWizard::tr("Source"));
auto vlayout = new QVBoxLayout; auto vlayout = new QVBoxLayout;
@@ -82,8 +97,10 @@ public:
auto path = new PathChooser; auto path = new PathChooser;
path->setExpectedKind(PathChooser::Any); path->setExpectedKind(PathChooser::Any);
vlayout->addWidget(path); vlayout->addWidget(path);
registerFieldWithName(kPath, path, "path", SIGNAL(pathChanged(QString))); connect(path, &PathChooser::pathChanged, this, [this, path] {
connect(path, &PathChooser::pathChanged, this, &SourcePage::updateWarnings); m_data->sourcePath = path->filePath();
updateWarnings();
});
m_info = new InfoLabel; m_info = new InfoLabel;
m_info->setType(InfoLabel::Error); m_info->setType(InfoLabel::Error);
@@ -99,7 +116,7 @@ public:
bool isComplete() const bool isComplete() const
{ {
const auto path = FilePath::fromVariant(field(kPath)); const FilePath path = m_data->sourcePath;
if (!QFile::exists(path.toString())) { if (!QFile::exists(path.toString())) {
m_info->setText(PluginInstallWizard::tr("File does not exist.")); m_info->setText(PluginInstallWizard::tr("File does not exist."));
return false; return false;
@@ -115,14 +132,118 @@ public:
return true; return true;
} }
int nextId() const
{
if (hasLibSuffix(m_data->sourcePath))
return WizardPage::nextId() + 1; // jump over check archive
return WizardPage::nextId();
}
InfoLabel *m_info = nullptr; InfoLabel *m_info = nullptr;
Data *m_data = nullptr;
};
class CheckArchivePage : public WizardPage
{
public:
CheckArchivePage(Data *data, QWidget *parent)
: WizardPage(parent)
, m_data(data)
{
setTitle(PluginInstallWizard::tr("Check Archive"));
auto vlayout = new QVBoxLayout;
setLayout(vlayout);
m_label = new InfoLabel;
m_cancelButton = new QPushButton(PluginInstallWizard::tr("Cancel"));
m_output = new QTextEdit;
m_output->setReadOnly(true);
auto hlayout = new QHBoxLayout;
hlayout->addWidget(m_label, 1);
hlayout->addStretch();
hlayout->addWidget(m_cancelButton);
vlayout->addLayout(hlayout);
vlayout->addWidget(m_output);
}
void initializePage()
{
m_isComplete = false;
emit completeChanged();
m_canceled = false;
m_tempDir = std::make_unique<TemporaryDirectory>("plugininstall");
m_data->extractedPath = FilePath::fromString(m_tempDir->path());
m_label->setText(PluginInstallWizard::tr("Checking archive..."));
// m_label->setType(InfoLabel::None);
m_cancelButton->setVisible(true);
m_output->clear();
m_archive = Archive::unarchive(m_data->sourcePath, FilePath::fromString(m_tempDir->path()));
if (!m_archive) {
m_label->setType(InfoLabel::Error);
m_label->setText(PluginInstallWizard::tr("The file is not an archive."));
return;
}
QObject::connect(m_archive, &Archive::outputReceived, this, [this](const QString &output) {
m_output->append(output);
});
QObject::connect(m_archive, &Archive::finished, this, [this](bool success) {
m_cancelButton->setVisible(false);
m_isComplete = success;
if (success) {
m_label->setType(InfoLabel::Ok);
m_label->setText(PluginInstallWizard::tr("Archive is ok."));
} else {
if (m_canceled) {
m_label->setType(InfoLabel::Information);
m_label->setText(PluginInstallWizard::tr("Canceled."));
} else {
m_label->setType(InfoLabel::Error);
m_label->setText(
PluginInstallWizard::tr("There was an error while unarchiving."));
}
}
m_archive = nullptr; // we don't own it
emit completeChanged();
});
QObject::connect(m_cancelButton, &QPushButton::clicked, m_archive, [this] {
m_canceled = true;
m_archive->cancel();
});
}
void cleanupPage()
{
// back button pressed
if (m_archive) {
m_archive->cancel();
m_archive = nullptr; // we don't own it
}
m_tempDir.reset();
}
bool isComplete() const { return m_isComplete; }
std::unique_ptr<TemporaryDirectory> m_tempDir;
Archive *m_archive = nullptr;
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 class InstallLocationPage : public WizardPage
{ {
public: public:
InstallLocationPage(QWidget *parent) InstallLocationPage(Data *data, QWidget *parent)
: WizardPage(parent) : WizardPage(parent)
, m_data(data)
{ {
setTitle(PluginInstallWizard::tr("Install Location")); setTitle(PluginInstallWizard::tr("Install Location"));
auto vlayout = new QVBoxLayout; auto vlayout = new QVBoxLayout;
@@ -162,31 +283,20 @@ public:
group->addButton(localInstall); group->addButton(localInstall);
group->addButton(appInstall); group->addButton(appInstall);
registerFieldWithName(kApplicationInstall, this);
setField(kApplicationInstall, false);
connect(appInstall, &QRadioButton::toggled, this, [this](bool toggled) { connect(appInstall, &QRadioButton::toggled, this, [this](bool toggled) {
setField(kApplicationInstall, toggled); m_data->installIntoApplication = toggled;
}); });
} }
Data *m_data = nullptr;
}; };
static FilePath pluginInstallPath(QWizard *wizard)
{
return FilePath::fromString(wizard->field(kApplicationInstall).toBool()
? ICore::pluginPath()
: ICore::userPluginPath());
}
static FilePath pluginFilePath(QWizard *wizard)
{
return FilePath::fromVariant(wizard->field(kPath));
}
class SummaryPage : public WizardPage class SummaryPage : public WizardPage
{ {
public: public:
SummaryPage(QWidget *parent) SummaryPage(Data *data, QWidget *parent)
: WizardPage(parent) : WizardPage(parent)
, m_data(data)
{ {
setTitle(PluginInstallWizard::tr("Summary")); setTitle(PluginInstallWizard::tr("Summary"));
@@ -200,13 +310,15 @@ public:
void initializePage() void initializePage()
{ {
m_summaryLabel->setText(PluginInstallWizard::tr("\"%1\" will be installed into \"%2\".") m_summaryLabel->setText(
.arg(pluginFilePath(wizard()).toUserOutput(), PluginInstallWizard::tr("\"%1\" will be installed into \"%2\".")
pluginInstallPath(wizard()).toUserOutput())); .arg(m_data->sourcePath.toUserOutput(),
pluginInstallPath(m_data->installIntoApplication).toUserOutput()));
} }
private: private:
QLabel *m_summaryLabel; QLabel *m_summaryLabel;
Data *m_data = nullptr;
}; };
static bool copyPluginFile(const FilePath &src, const FilePath &dest) static bool copyPluginFile(const FilePath &src, const FilePath &dest)
@@ -243,23 +355,36 @@ bool PluginInstallWizard::exec()
Wizard wizard(ICore::dialogParent()); Wizard wizard(ICore::dialogParent());
wizard.setWindowTitle(tr("Install Plugin")); wizard.setWindowTitle(tr("Install Plugin"));
auto filePage = new SourcePage(&wizard); Data data;
auto filePage = new SourcePage(&data, &wizard);
wizard.addPage(filePage); wizard.addPage(filePage);
auto installLocationPage = new InstallLocationPage(&wizard); auto checkArchivePage = new CheckArchivePage(&data, &wizard);
wizard.addPage(checkArchivePage);
auto installLocationPage = new InstallLocationPage(&data, &wizard);
wizard.addPage(installLocationPage); wizard.addPage(installLocationPage);
auto summaryPage = new SummaryPage(&wizard); auto summaryPage = new SummaryPage(&data, &wizard);
wizard.addPage(summaryPage); wizard.addPage(summaryPage);
if (wizard.exec()) { if (wizard.exec()) {
const FilePath path = pluginFilePath(&wizard); const FilePath installPath = pluginInstallPath(data.installIntoApplication);
const FilePath installPath = pluginInstallPath(&wizard); if (hasLibSuffix(data.sourcePath)) {
if (hasLibSuffix(path)) { return copyPluginFile(data.sourcePath, installPath);
if (copyPluginFile(path, installPath)) } else {
return true; QString error;
} else if (Archive::supportsFile(path)) { if (!FileUtils::copyRecursively(data.extractedPath,
if (Archive::unarchive(path, installPath, ICore::dialogParent())) installPath,
&error,
FileUtils::CopyAskingForOverwrite(
ICore::dialogParent()))) {
QMessageBox::warning(ICore::dialogParent(),
PluginInstallWizard::tr("Failed to Copy Plugin Files"),
error);
return false;
}
return true; return true;
} }
} }