AndroidSdkDownloader: Reuse TaskTree

Use it for downloading and unarchiving chain of tasks.

Change-Id: I28d4c07a49933665158452ec836a4a6b2c5e23b0
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2023-06-07 11:15:12 +02:00
parent a4b60e5f13
commit 804ad5a7f6
3 changed files with 110 additions and 107 deletions

View File

@@ -5,6 +5,8 @@
#include "androidsdkdownloader.h" #include "androidsdkdownloader.h"
#include "androidtr.h" #include "androidtr.h"
#include <solutions/tasking/networkquery.h>
#include <utils/archive.h> #include <utils/archive.h>
#include <utils/filepath.h> #include <utils/filepath.h>
#include <utils/networkaccessmanager.h> #include <utils/networkaccessmanager.h>
@@ -13,6 +15,7 @@
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QProgressDialog>
#include <QStandardPaths> #include <QStandardPaths>
using namespace Utils; using namespace Utils;
@@ -32,14 +35,14 @@ AndroidSdkDownloader::~AndroidSdkDownloader() = default;
static bool isHttpRedirect(QNetworkReply *reply) static bool isHttpRedirect(QNetworkReply *reply)
{ {
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305
|| statusCode == 307 || statusCode == 308; || statusCode == 307 || statusCode == 308;
} }
static FilePath sdkFromUrl(const QUrl &url) static FilePath sdkFromUrl(const QUrl &url)
{ {
QString path = url.path(); const QString path = url.path();
QString basename = QFileInfo(path).fileName(); QString basename = QFileInfo(path).fileName();
if (basename.isEmpty()) if (basename.isEmpty())
@@ -57,6 +60,7 @@ static FilePath sdkFromUrl(const QUrl &url)
/ basename; / basename;
} }
// TODO: Make it a separate async task in a chain?
static std::optional<QString> saveToDisk(const FilePath &filename, QIODevice *data) static std::optional<QString> saveToDisk(const FilePath &filename, QIODevice *data)
{ {
QFile file(filename.toString()); QFile file(filename.toString());
@@ -68,6 +72,7 @@ static std::optional<QString> saveToDisk(const FilePath &filename, QIODevice *da
return {}; return {};
} }
// TODO: Make it a separate async task in a chain?
static bool verifyFileIntegrity(const FilePath fileName, const QByteArray &sha256) static bool verifyFileIntegrity(const FilePath fileName, const QByteArray &sha256)
{ {
QFile file(fileName.toString()); QFile file(fileName.toString());
@@ -79,15 +84,6 @@ static bool verifyFileIntegrity(const FilePath fileName, const QByteArray &sha25
return false; return false;
} }
#if QT_CONFIG(ssl)
void AndroidSdkDownloader::sslErrors(const QList<QSslError> &sslErrors)
{
for (const QSslError &error : sslErrors)
qCDebug(sdkDownloaderLog, "SSL error: %s\n", qPrintable(error.errorString()));
cancelWithError(Tr::tr("Encountered SSL errors, download is aborted."));
}
#endif
void AndroidSdkDownloader::downloadAndExtractSdk() void AndroidSdkDownloader::downloadAndExtractSdk()
{ {
if (m_androidConfig.sdkToolsUrl().isEmpty()) { if (m_androidConfig.sdkToolsUrl().isEmpty()) {
@@ -95,46 +91,106 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
return; return;
} }
const QNetworkRequest request(m_androidConfig.sdkToolsUrl()); m_progressDialog.reset(new QProgressDialog(Tr::tr("Downloading SDK Tools package..."),
m_reply = NetworkAccessManager::instance()->get(request); Tr::tr("Cancel"), 0, 100, Core::ICore::dialogParent()));
connect(m_reply, &QNetworkReply::finished, this, &AndroidSdkDownloader::downloadFinished);
#if QT_CONFIG(ssl)
connect(m_reply, &QNetworkReply::sslErrors, this, &AndroidSdkDownloader::sslErrors);
#endif
m_progressDialog = new QProgressDialog(Tr::tr("Downloading SDK Tools package..."), Tr::tr("Cancel"),
0, 100, Core::ICore::dialogParent());
m_progressDialog->setWindowModality(Qt::ApplicationModal); m_progressDialog->setWindowModality(Qt::ApplicationModal);
m_progressDialog->setWindowTitle(dialogTitle()); m_progressDialog->setWindowTitle(dialogTitle());
m_progressDialog->setFixedSize(m_progressDialog->sizeHint()); m_progressDialog->setFixedSize(m_progressDialog->sizeHint());
connect(m_progressDialog.get(), &QProgressDialog::canceled, this, [this] {
connect(m_reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 max) { m_progressDialog.release()->deleteLater();
m_progressDialog->setRange(0, max); m_taskTree.reset();
m_progressDialog->setValue(received);
}); });
connect(m_progressDialog, &QProgressDialog::canceled, this, &AndroidSdkDownloader::cancel); using namespace Tasking;
connect(this, &AndroidSdkDownloader::sdkPackageWriteFinished, this, [this] { TreeStorage<std::optional<FilePath>> storage;
if (!Archive::supportsFile(m_sdkFilename))
return; const auto onQuerySetup = [this](NetworkQuery &query) {
const FilePath extractDir = m_sdkFilename.parentDir(); query.setRequest(QNetworkRequest(m_androidConfig.sdkToolsUrl()));
m_archive.reset(new Archive(m_sdkFilename, extractDir)); query.setNetworkAccessManager(NetworkAccessManager::instance());
if (m_archive->isValid()) { NetworkQuery *queryPtr = &query;
connect(m_archive.get(), &Archive::finished, this, [this, extractDir](bool success) { connect(queryPtr, &NetworkQuery::started, this, [this, queryPtr] {
if (success) { QNetworkReply *reply = queryPtr->reply();
// Save the extraction path temporarily which can be used by sdkmanager if (!reply)
// to install essential packages at firt time setup. return;
m_androidConfig.setTemporarySdkToolsPath( connect(reply, &QNetworkReply::downloadProgress,
extractDir.pathAppended(Constants::cmdlineToolsName)); this, [this](qint64 received, qint64 max) {
emit sdkExtracted(); m_progressDialog->setRange(0, max);
} m_progressDialog->setValue(received);
m_archive.release()->deleteLater();
}); });
m_archive->unarchive(); #if QT_CONFIG(ssl)
connect(reply, &QNetworkReply::sslErrors,
this, [this, reply](const QList<QSslError> &sslErrors) {
for (const QSslError &error : sslErrors)
qCDebug(sdkDownloaderLog, "SSL error: %s\n", qPrintable(error.errorString()));
logError(Tr::tr("Encountered SSL errors, download is aborted."));
reply->abort();
});
#endif
});
};
const auto onQueryDone = [this, storage](const NetworkQuery &query) {
QNetworkReply *reply = query.reply();
QTC_ASSERT(reply, return);
const QUrl url = reply->url();
if (isHttpRedirect(reply)) {
logError(Tr::tr("Download from %1 was redirected.").arg(url.toString()));
return;
} }
}); const FilePath sdkFileName = sdkFromUrl(url);
const std::optional<QString> saveResult = saveToDisk(sdkFileName, reply);
if (saveResult) {
logError(*saveResult);
return;
}
*storage = sdkFileName;
};
const auto onQueryError = [this](const NetworkQuery &query) {
QNetworkReply *reply = query.reply();
QTC_ASSERT(reply, return);
const QUrl url = reply->url();
logError(Tr::tr("Downloading Android SDK Tools from URL %1 has failed: %2.")
.arg(url.toString(), reply->errorString()));
};
const auto onUnarchiveSetup = [this, storage](Unarchiver &unarchiver) {
m_progressDialog.reset();
if (!*storage)
return TaskAction::StopWithError;
const FilePath sdkFileName = **storage;
if (!verifyFileIntegrity(sdkFileName, m_androidConfig.getSdkToolsSha256())) {
logError(Tr::tr("Verifying the integrity of the downloaded file has failed."));
return TaskAction::StopWithError;
}
const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName);
if (!sourceAndCommand) {
logError(sourceAndCommand.error());
return TaskAction::StopWithError;
}
unarchiver.setSourceAndCommand(*sourceAndCommand);
unarchiver.setDestDir(sdkFileName.parentDir());
return TaskAction::Continue;
};
const auto onUnarchiverDone = [this, storage](const Unarchiver &) {
m_androidConfig.setTemporarySdkToolsPath(
(*storage)->parentDir().pathAppended(Constants::cmdlineToolsName));
QMetaObject::invokeMethod(this, [this] { emit sdkExtracted(); }, Qt::QueuedConnection);
};
const Group root {
Storage(storage),
NetworkQueryTask(onQuerySetup, onQueryDone, onQueryError),
UnarchiverTask(onUnarchiveSetup, onUnarchiverDone)
};
m_taskTree.reset(new TaskTree(root));
const auto onDone = [this] {
m_taskTree.release()->deleteLater();
m_progressDialog.reset();
};
connect(m_taskTree.get(), &TaskTree::done, this, onDone);
connect(m_taskTree.get(), &TaskTree::errorOccurred, this, onDone);
m_taskTree->start();
} }
QString AndroidSdkDownloader::dialogTitle() QString AndroidSdkDownloader::dialogTitle()
@@ -142,54 +198,11 @@ QString AndroidSdkDownloader::dialogTitle()
return Tr::tr("Download SDK Tools"); return Tr::tr("Download SDK Tools");
} }
void AndroidSdkDownloader::cancel()
{
if (m_reply) {
m_reply->abort();
m_reply->deleteLater();
m_reply = nullptr;
}
if (m_progressDialog)
m_progressDialog->cancel();
}
void AndroidSdkDownloader::cancelWithError(const QString &error)
{
cancel();
logError(error);
}
void AndroidSdkDownloader::logError(const QString &error) void AndroidSdkDownloader::logError(const QString &error)
{ {
qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data()); qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data());
emit sdkDownloaderError(error); QMetaObject::invokeMethod(this, [this, error] { emit sdkDownloaderError(error); },
} Qt::QueuedConnection);
void AndroidSdkDownloader::downloadFinished()
{
QUrl url = m_reply->url();
if (m_reply->error()) {
cancelWithError(QString(Tr::tr("Downloading Android SDK Tools from URL %1 has failed: %2."))
.arg(url.toString(), m_reply->errorString()));
} else {
if (isHttpRedirect(m_reply)) {
cancelWithError(QString(Tr::tr("Download from %1 was redirected.")).arg(url.toString()));
} else {
m_sdkFilename = sdkFromUrl(url);
const std::optional<QString> saveResult = saveToDisk(m_sdkFilename, m_reply);
if (saveResult) {
cancelWithError(*saveResult);
} else if (!verifyFileIntegrity(m_sdkFilename, m_androidConfig.getSdkToolsSha256())) {
cancelWithError(Tr::tr("Writing and verifying the integrity of the "
"downloaded file has failed."));
} else {
emit sdkPackageWriteFinished();
}
}
}
m_reply->deleteLater();
m_reply = nullptr;
} }
} // namespace Android::Internal } // namespace Android::Internal

View File

@@ -5,14 +5,13 @@
#include "androidconfigurations.h" #include "androidconfigurations.h"
#include <QNetworkReply>
#include <QObject> #include <QObject>
#include <QProgressDialog>
namespace Utils { QT_BEGIN_NAMESPACE
class Archive; class QProgressDialog;
class FilePath; QT_END_NAMESPACE
}
namespace Tasking { class TaskTree; }
namespace Android::Internal { namespace Android::Internal {
@@ -23,30 +22,20 @@ class AndroidSdkDownloader : public QObject
public: public:
AndroidSdkDownloader(); AndroidSdkDownloader();
~AndroidSdkDownloader(); ~AndroidSdkDownloader();
void downloadAndExtractSdk(); void downloadAndExtractSdk();
static QString dialogTitle(); static QString dialogTitle();
void cancel();
signals: signals:
void sdkPackageWriteFinished();
void sdkExtracted(); void sdkExtracted();
void sdkDownloaderError(const QString &error); void sdkDownloaderError(const QString &error);
private: private:
void cancelWithError(const QString &error);
void logError(const QString &error); void logError(const QString &error);
void downloadFinished();
#if QT_CONFIG(ssl)
void sslErrors(const QList<QSslError> &errors);
#endif
QNetworkReply *m_reply = nullptr;
Utils::FilePath m_sdkFilename;
QProgressDialog *m_progressDialog = nullptr;
AndroidConfig &m_androidConfig; AndroidConfig &m_androidConfig;
std::unique_ptr<Utils::Archive> m_archive; std::unique_ptr<QProgressDialog> m_progressDialog;
std::unique_ptr<Tasking::TaskTree> m_taskTree;
}; };
} // namespace Android::Internal } // namespace Android::Internal

View File

@@ -30,6 +30,7 @@
#include <QListWidget> #include <QListWidget>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog>
#include <QPushButton> #include <QPushButton>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTimer> #include <QTimer>