Archive: Avoid calling blocking stopProcess

Make Archive constructor public.
Make the caller responsible for deleting the Archive object.

Don't automatially run the unarchive process when constructing
Archive object. Provide a start() method, to be called after
the caller has connected to Archive signals.

Add Archive::isValid() method.

Remove Archive::unarchive() gui overload, as it's unused.

Make sure we don't leak the Archive object in
AndroidSdkDownloader.

Change-Id: Idf67262554cdfef50aef4a2234b6a5089110f9a2
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2022-03-24 10:29:02 +01:00
parent 41f9962ea6
commit ed94e0c066
6 changed files with 117 additions and 155 deletions

View File

@@ -26,16 +26,11 @@
#include "archive.h" #include "archive.h"
#include "algorithm.h" #include "algorithm.h"
#include "checkablemessagebox.h"
#include "environment.h"
#include "mimeutils.h" #include "mimeutils.h"
#include "qtcassert.h" #include "qtcassert.h"
#include "qtcprocess.h" #include "qtcprocess.h"
#include <QDir>
#include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QTimer>
namespace Utils { namespace Utils {
@@ -163,99 +158,48 @@ bool Archive::supportsFile(const FilePath &filePath, QString *reason)
return true; return true;
} }
bool Archive::unarchive(const FilePath &src, const FilePath &dest, QWidget *parent) Archive::Archive(const FilePath &src, const FilePath &dest)
{
Archive *archive = unarchive(src, dest);
QTC_ASSERT(archive, return false);
CheckableMessageBox box(parent);
box.setIcon(QMessageBox::Information);
box.setWindowTitle(tr("Unarchiving File"));
box.setText(tr("Unzipping \"%1\" to \"%2\".").arg(src.toUserOutput(), dest.toUserOutput()));
box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
box.button(QDialogButtonBox::Ok)->setEnabled(false);
box.setCheckBoxVisible(false);
QObject::connect(archive, &Archive::outputReceived, &box, [&box](const QString &output) {
box.setDetailedText(box.detailedText() + output);
});
bool success = false;
QObject::connect(archive, &Archive::finished, [&box, &success](bool ret) {
box.button(QDialogButtonBox::Ok)->setEnabled(true);
box.button(QDialogButtonBox::Cancel)->setEnabled(false);
success = ret;
});
QObject::connect(&box, &QMessageBox::rejected, archive, &Archive::cancel);
box.exec();
return success;
}
Archive *Archive::unarchive(const FilePath &src, const FilePath &dest)
{ {
const Utils::optional<Tool> tool = unzipTool(src, dest); const Utils::optional<Tool> tool = unzipTool(src, dest);
QTC_ASSERT(tool, return nullptr); if (!tool)
auto archive = new Archive;
const FilePath workingDirectory = dest.absolutePath();
workingDirectory.ensureWritableDir();
archive->m_process = new QtcProcess;
archive->m_process->setProcessChannelMode(QProcess::MergedChannels);
QObject::connect(
archive->m_process,
&QtcProcess::readyReadStandardOutput,
archive,
[archive]() {
if (!archive->m_process)
return; return;
emit archive->outputReceived(QString::fromUtf8( m_commandLine = tool->command;
archive->m_process->readAllStandardOutput())); m_workingDirectory = dest.absoluteFilePath();
},
Qt::QueuedConnection);
QObject::connect(
archive->m_process,
&QtcProcess::finished,
archive,
[archive] {
if (!archive->m_process)
return;
emit archive->finished(archive->m_process->result() == ProcessResult::FinishedWithSuccess);
archive->m_process->deleteLater();
archive->m_process = nullptr;
archive->deleteLater();
},
Qt::QueuedConnection);
QObject::connect(
archive->m_process,
&QtcProcess::errorOccurred,
archive,
[archive](QProcess::ProcessError) {
if (!archive->m_process)
return;
emit archive->outputReceived(tr("Command failed."));
emit archive->finished(false);
archive->m_process->deleteLater();
archive->m_process = nullptr;
archive->deleteLater();
},
Qt::QueuedConnection);
QTimer::singleShot(0, archive, [archive, tool, workingDirectory] {
emit archive->outputReceived(
tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>")
.arg(tool->command.toUserOutput(), workingDirectory.toUserOutput()));
});
archive->m_process->setCommand(tool->command);
archive->m_process->setWorkingDirectory(workingDirectory);
archive->m_process->start();
return archive;
} }
void Archive::cancel() Archive::~Archive() = default;
bool Archive::isValid() const
{ {
if (m_process) return !m_commandLine.isEmpty();
m_process->stopProcess(); }
void Archive::unarchive()
{
QTC_ASSERT(isValid(), return);
QTC_ASSERT(!m_process, return);
m_workingDirectory.ensureWritableDir();
m_process.reset(new QtcProcess);
m_process->setProcessChannelMode(QProcess::MergedChannels);
QObject::connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this] {
emit outputReceived(QString::fromUtf8(m_process->readAllStandardOutput()));
});
QObject::connect(m_process.get(), &QtcProcess::finished, this, [this] {
emit finished(m_process->result() == ProcessResult::FinishedWithSuccess);
});
QObject::connect(m_process.get(), &QtcProcess::errorOccurred, this, [this] {
emit outputReceived(tr("Command failed."));
emit finished(false);
});
emit outputReceived(tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>")
.arg(m_commandLine.toUserOutput(), m_workingDirectory.toUserOutput()));
m_process->setCommand(m_commandLine);
m_process->setWorkingDirectory(m_workingDirectory);
m_process->start();
} }
} // namespace Utils } // namespace Utils

View File

@@ -27,6 +27,7 @@
#include "utils_global.h" #include "utils_global.h"
#include "commandline.h"
#include "fileutils.h" #include "fileutils.h"
#include <QObject> #include <QObject>
@@ -39,20 +40,22 @@ class QTCREATOR_UTILS_EXPORT Archive : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
static bool supportsFile(const FilePath &filePath, QString *reason = nullptr); Archive(const FilePath &src, const FilePath &dest);
static bool unarchive(const FilePath &src, const FilePath &dest, QWidget *parent); ~Archive();
static Archive *unarchive(const FilePath &src, const FilePath &dest);
void cancel(); bool isValid() const;
void unarchive();
static bool supportsFile(const FilePath &filePath, QString *reason = nullptr);
signals: signals:
void outputReceived(const QString &output); void outputReceived(const QString &output);
void finished(bool success); void finished(bool success);
private: private:
Archive() = default; Utils::CommandLine m_commandLine;
Utils::FilePath m_workingDirectory;
QtcProcess *m_process = nullptr; std::unique_ptr<QtcProcess> m_process;
}; };
} // namespace Utils } // namespace Utils

View File

@@ -54,6 +54,8 @@ AndroidSdkDownloader::AndroidSdkDownloader()
connect(&m_manager, &QNetworkAccessManager::finished, this, &AndroidSdkDownloader::downloadFinished); connect(&m_manager, &QNetworkAccessManager::finished, this, &AndroidSdkDownloader::downloadFinished);
} }
AndroidSdkDownloader::~AndroidSdkDownloader() = default;
#if QT_CONFIG(ssl) #if QT_CONFIG(ssl)
void AndroidSdkDownloader::sslErrors(const QList<QSslError> &sslErrors) void AndroidSdkDownloader::sslErrors(const QList<QSslError> &sslErrors)
{ {
@@ -91,9 +93,12 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
connect(m_progressDialog, &QProgressDialog::canceled, this, &AndroidSdkDownloader::cancel); connect(m_progressDialog, &QProgressDialog::canceled, this, &AndroidSdkDownloader::cancel);
connect(this, &AndroidSdkDownloader::sdkPackageWriteFinished, this, [this]() { connect(this, &AndroidSdkDownloader::sdkPackageWriteFinished, this, [this]() {
if (!Archive::supportsFile(m_sdkFilename))
return;
const FilePath extractDir = m_sdkFilename.parentDir(); const FilePath extractDir = m_sdkFilename.parentDir();
if (Archive *archive = Archive::unarchive(m_sdkFilename, extractDir)) { m_archive.reset(new Archive(m_sdkFilename, extractDir));
connect(archive, &Archive::finished, [this, extractDir](bool success) { if (m_archive->isValid()) {
connect(m_archive.get(), &Archive::finished, this, [this, extractDir](bool success) {
if (success) { if (success) {
// Save the extraction path temporarily which can be used by sdkmanager // Save the extraction path temporarily which can be used by sdkmanager
// to install essential packages at firt time setup. // to install essential packages at firt time setup.
@@ -101,7 +106,9 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
extractDir.pathAppended(Constants::cmdlineToolsName)); extractDir.pathAppended(Constants::cmdlineToolsName));
emit sdkExtracted(); emit sdkExtracted();
} }
m_archive.release()->deleteLater();
}); });
m_archive->unarchive();
} }
}); });
} }

View File

@@ -32,7 +32,10 @@
#include <QObject> #include <QObject>
#include <QProgressDialog> #include <QProgressDialog>
namespace Utils { class FilePath; } namespace Utils {
class Archive;
class FilePath;
}
namespace Android { namespace Android {
namespace Internal { namespace Internal {
@@ -43,6 +46,7 @@ class AndroidSdkDownloader : public QObject
public: public:
AndroidSdkDownloader(); AndroidSdkDownloader();
~AndroidSdkDownloader();
void downloadAndExtractSdk(); void downloadAndExtractSdk();
static QString dialogTitle(); static QString dialogTitle();
@@ -72,6 +76,7 @@ private:
Utils::FilePath m_sdkFilename; Utils::FilePath m_sdkFilename;
QProgressDialog *m_progressDialog = nullptr; QProgressDialog *m_progressDialog = nullptr;
AndroidConfig &m_androidConfig; AndroidConfig &m_androidConfig;
std::unique_ptr<Utils::Archive> m_archive;
}; };
} // Internal } // Internal

View File

@@ -207,19 +207,30 @@ public:
m_cancelButton->setVisible(true); m_cancelButton->setVisible(true);
m_output->clear(); m_output->clear();
m_archive = Archive::unarchive(m_data->sourcePath, m_tempDir->path()); m_archive.reset(new Archive(m_data->sourcePath, m_tempDir->path()));
if (!m_archive->isValid()) {
if (!m_archive) {
m_label->setType(InfoLabel::Error); m_label->setType(InfoLabel::Error);
m_label->setText(PluginInstallWizard::tr("The file is not an archive.")); m_label->setText(PluginInstallWizard::tr("The file is not an archive."));
return; return;
} }
QObject::connect(m_archive, &Archive::outputReceived, this, [this](const QString &output) { QObject::connect(m_archive.get(), &Archive::outputReceived, this,
[this](const QString &output) {
m_output->append(output); m_output->append(output);
}); });
QObject::connect(m_archive, &Archive::finished, this, [this](bool success) { QObject::connect(m_archive.get(), &Archive::finished, this, [this](bool success) {
m_archive = nullptr; // we don't own it 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(); m_cancelButton->disconnect();
if (!success) { // unarchiving failed if (!success) { // unarchiving failed
m_cancelButton->setVisible(false); m_cancelButton->setVisible(false);
@@ -256,11 +267,6 @@ public:
m_archiveCheck.cancel(); m_archiveCheck.cancel();
}); });
} }
});
QObject::connect(m_cancelButton, &QPushButton::clicked, m_archive, [this] {
m_canceled = true;
m_archive->cancel();
});
} }
// Async. Result is set if any issue was found. // Async. Result is set if any issue was found.
@@ -311,11 +317,7 @@ public:
{ {
// back button pressed // back button pressed
m_cancelButton->disconnect(); m_cancelButton->disconnect();
if (m_archive) { m_archive.reset();
m_archive->disconnect();
m_archive->cancel();
m_archive = nullptr; // we don't own it
}
if (m_archiveCheck.isRunning()) { if (m_archiveCheck.isRunning()) {
m_archiveCheck.cancel(); m_archiveCheck.cancel();
m_archiveCheck.waitForFinished(); m_archiveCheck.waitForFinished();
@@ -326,7 +328,7 @@ public:
bool isComplete() const final { return m_isComplete; } bool isComplete() const final { return m_isComplete; }
std::unique_ptr<TemporaryDirectory> m_tempDir; std::unique_ptr<TemporaryDirectory> m_tempDir;
Archive *m_archive = nullptr; std::unique_ptr<Archive> m_archive;
QFuture<ArchiveIssue> m_archiveCheck; QFuture<ArchiveIssue> m_archiveCheck;
InfoLabel *m_label = nullptr; InfoLabel *m_label = nullptr;
QPushButton *m_cancelButton = nullptr; QPushButton *m_cancelButton = nullptr;

View File

@@ -378,9 +378,9 @@ void FileExtractor::extract()
// Create a new directory to generate a proper creation date // Create a new directory to generate a proper creation date
targetDir.mkdir(targetFolder); targetDir.mkdir(targetFolder);
Utils::Archive *archive = Utils::Archive::unarchive(m_sourceFile, m_targetPath); Utils::Archive *archive = new Utils::Archive(m_sourceFile, m_targetPath);
QTC_ASSERT(archive->isValid(), delete archive; return);
archive->setParent(this); archive->setParent(this);
QTC_ASSERT(archive, return );
m_timer.start(); m_timer.start();
qint64 bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable(); qint64 bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
@@ -429,6 +429,7 @@ void FileExtractor::extract()
emit targetFolderExistsChanged(); emit targetFolderExistsChanged();
emit finishedChanged(); emit finishedChanged();
QTC_ASSERT(ret, ;); QTC_CHECK(ret);
}); });
archive->unarchive();
} }