2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2020 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2019-12-23 16:13:23 +02:00
|
|
|
|
2020-11-28 15:44:37 +02:00
|
|
|
#include "androidconstants.h"
|
2022-10-06 17:53:35 +02:00
|
|
|
#include "androidsdkdownloader.h"
|
|
|
|
|
#include "androidtr.h"
|
2020-11-28 15:44:37 +02:00
|
|
|
|
2023-06-07 11:15:12 +02:00
|
|
|
#include <solutions/tasking/networkquery.h>
|
|
|
|
|
|
2021-12-20 00:09:00 +01:00
|
|
|
#include <utils/filepath.h>
|
2023-06-07 10:55:28 +02:00
|
|
|
#include <utils/networkaccessmanager.h>
|
2023-06-07 10:28:57 +02:00
|
|
|
#include <utils/unarchiver.h>
|
2021-12-20 00:09:00 +01:00
|
|
|
|
2020-11-30 15:13:49 +01:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
|
2021-10-29 17:29:18 +02:00
|
|
|
#include <QCryptographicHash>
|
2019-12-23 16:13:23 +02:00
|
|
|
#include <QLoggingCategory>
|
2023-06-07 11:15:12 +02:00
|
|
|
#include <QProgressDialog>
|
2019-12-23 16:13:23 +02:00
|
|
|
#include <QStandardPaths>
|
|
|
|
|
|
2024-01-13 13:36:39 +01:00
|
|
|
using namespace Tasking;
|
2021-09-15 15:16:56 +02:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2023-06-07 10:47:13 +02:00
|
|
|
namespace { Q_LOGGING_CATEGORY(sdkDownloaderLog, "qtc.android.sdkDownloader", QtWarningMsg) }
|
2019-12-23 16:13:23 +02:00
|
|
|
|
2023-06-07 10:47:13 +02:00
|
|
|
namespace Android::Internal {
|
2019-12-23 16:13:23 +02:00
|
|
|
/**
|
|
|
|
|
* @class SdkDownloader
|
|
|
|
|
* @brief Download Android SDK tools package from within Qt Creator.
|
|
|
|
|
*/
|
2020-03-16 20:14:56 +02:00
|
|
|
AndroidSdkDownloader::AndroidSdkDownloader()
|
2021-10-25 15:58:02 +03:00
|
|
|
: m_androidConfig(AndroidConfigurations::currentConfig())
|
2024-01-13 13:36:39 +01:00
|
|
|
{
|
|
|
|
|
connect(&m_taskTreeRunner, &TaskTreeRunner::done, this, [this] { m_progressDialog.reset(); });
|
|
|
|
|
}
|
2019-12-23 16:13:23 +02:00
|
|
|
|
2022-03-24 10:29:02 +01:00
|
|
|
AndroidSdkDownloader::~AndroidSdkDownloader() = default;
|
|
|
|
|
|
2023-06-07 11:09:19 +02:00
|
|
|
static bool isHttpRedirect(QNetworkReply *reply)
|
|
|
|
|
{
|
2023-06-07 11:15:12 +02:00
|
|
|
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
2023-06-07 11:09:19 +02:00
|
|
|
return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305
|
|
|
|
|
|| statusCode == 307 || statusCode == 308;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FilePath sdkFromUrl(const QUrl &url)
|
|
|
|
|
{
|
2023-06-07 11:15:12 +02:00
|
|
|
const QString path = url.path();
|
2023-06-07 11:09:19 +02:00
|
|
|
QString basename = QFileInfo(path).fileName();
|
|
|
|
|
|
|
|
|
|
if (basename.isEmpty())
|
|
|
|
|
basename = "sdk-tools.zip";
|
|
|
|
|
|
2023-09-01 11:23:56 +02:00
|
|
|
if (QFileInfo::exists(basename)) {
|
2023-06-07 11:09:19 +02:00
|
|
|
int i = 0;
|
|
|
|
|
basename += '.';
|
2023-09-01 11:23:56 +02:00
|
|
|
while (QFileInfo::exists(basename + QString::number(i)))
|
2023-06-07 11:09:19 +02:00
|
|
|
++i;
|
|
|
|
|
basename += QString::number(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FilePath::fromString(QStandardPaths::writableLocation(QStandardPaths::TempLocation))
|
|
|
|
|
/ basename;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-07 11:15:12 +02:00
|
|
|
// TODO: Make it a separate async task in a chain?
|
2023-06-07 11:09:19 +02:00
|
|
|
static std::optional<QString> saveToDisk(const FilePath &filename, QIODevice *data)
|
|
|
|
|
{
|
|
|
|
|
QFile file(filename.toString());
|
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
2023-10-16 09:29:21 +02:00
|
|
|
return Tr::tr("Could not open \"%1\" for writing: %2.")
|
2023-06-07 11:09:19 +02:00
|
|
|
.arg(filename.toUserOutput(), file.errorString());
|
|
|
|
|
}
|
|
|
|
|
file.write(data->readAll());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-07 11:15:12 +02:00
|
|
|
// TODO: Make it a separate async task in a chain?
|
2023-06-07 11:09:19 +02:00
|
|
|
static bool verifyFileIntegrity(const FilePath fileName, const QByteArray &sha256)
|
|
|
|
|
{
|
|
|
|
|
QFile file(fileName.toString());
|
|
|
|
|
if (file.open(QFile::ReadOnly)) {
|
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Sha256);
|
|
|
|
|
if (hash.addData(&file))
|
|
|
|
|
return hash.result() == sha256;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-28 15:44:37 +02:00
|
|
|
void AndroidSdkDownloader::downloadAndExtractSdk()
|
2019-12-23 16:13:23 +02:00
|
|
|
{
|
2020-03-16 20:14:56 +02:00
|
|
|
if (m_androidConfig.sdkToolsUrl().isEmpty()) {
|
2022-10-06 17:53:35 +02:00
|
|
|
logError(Tr::tr("The SDK Tools download URL is empty."));
|
2019-12-23 16:13:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-07 11:15:12 +02:00
|
|
|
m_progressDialog.reset(new QProgressDialog(Tr::tr("Downloading SDK Tools package..."),
|
|
|
|
|
Tr::tr("Cancel"), 0, 100, Core::ICore::dialogParent()));
|
2020-11-30 15:13:49 +01:00
|
|
|
m_progressDialog->setWindowModality(Qt::ApplicationModal);
|
2019-12-23 16:13:23 +02:00
|
|
|
m_progressDialog->setWindowTitle(dialogTitle());
|
|
|
|
|
m_progressDialog->setFixedSize(m_progressDialog->sizeHint());
|
2023-06-09 10:00:49 +02:00
|
|
|
m_progressDialog->setAutoClose(false);
|
2023-06-07 11:15:12 +02:00
|
|
|
connect(m_progressDialog.get(), &QProgressDialog::canceled, this, [this] {
|
|
|
|
|
m_progressDialog.release()->deleteLater();
|
2024-01-13 13:36:39 +01:00
|
|
|
m_taskTreeRunner.reset();
|
2019-12-23 16:13:23 +02:00
|
|
|
});
|
|
|
|
|
|
2023-11-19 14:50:55 +01:00
|
|
|
Storage<std::optional<FilePath>> storage;
|
2023-06-07 11:15:12 +02:00
|
|
|
|
|
|
|
|
const auto onQuerySetup = [this](NetworkQuery &query) {
|
|
|
|
|
query.setRequest(QNetworkRequest(m_androidConfig.sdkToolsUrl()));
|
|
|
|
|
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
|
|
|
|
NetworkQuery *queryPtr = &query;
|
|
|
|
|
connect(queryPtr, &NetworkQuery::started, this, [this, queryPtr] {
|
|
|
|
|
QNetworkReply *reply = queryPtr->reply();
|
|
|
|
|
if (!reply)
|
|
|
|
|
return;
|
|
|
|
|
connect(reply, &QNetworkReply::downloadProgress,
|
|
|
|
|
this, [this](qint64 received, qint64 max) {
|
|
|
|
|
m_progressDialog->setRange(0, max);
|
|
|
|
|
m_progressDialog->setValue(received);
|
2021-12-20 00:09:00 +01:00
|
|
|
});
|
2023-06-07 11:15:12 +02:00
|
|
|
#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
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-11-03 09:42:55 +01:00
|
|
|
const auto onQueryDone = [this, storage](const NetworkQuery &query, DoneWith result) {
|
2023-06-07 11:15:12 +02:00
|
|
|
QNetworkReply *reply = query.reply();
|
|
|
|
|
QTC_ASSERT(reply, return);
|
|
|
|
|
const QUrl url = reply->url();
|
2023-11-03 09:42:55 +01:00
|
|
|
if (result != DoneWith::Success) {
|
2023-11-02 16:14:50 +01:00
|
|
|
logError(Tr::tr("Downloading Android SDK Tools from URL %1 has failed: %2.")
|
|
|
|
|
.arg(url.toString(), reply->errorString()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-07 11:15:12 +02:00
|
|
|
if (isHttpRedirect(reply)) {
|
|
|
|
|
logError(Tr::tr("Download from %1 was redirected.").arg(url.toString()));
|
|
|
|
|
return;
|
2019-12-23 16:13:23 +02:00
|
|
|
}
|
2023-06-07 11:15:12 +02:00
|
|
|
const FilePath sdkFileName = sdkFromUrl(url);
|
|
|
|
|
const std::optional<QString> saveResult = saveToDisk(sdkFileName, reply);
|
|
|
|
|
if (saveResult) {
|
|
|
|
|
logError(*saveResult);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
*storage = sdkFileName;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onUnarchiveSetup = [this, storage](Unarchiver &unarchiver) {
|
2023-06-09 10:00:49 +02:00
|
|
|
m_progressDialog->setRange(0, 0);
|
|
|
|
|
m_progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package..."));
|
2023-06-07 11:15:12 +02:00
|
|
|
if (!*storage)
|
2023-06-22 12:42:43 +02:00
|
|
|
return SetupResult::StopWithError;
|
2023-06-07 11:15:12 +02:00
|
|
|
const FilePath sdkFileName = **storage;
|
|
|
|
|
if (!verifyFileIntegrity(sdkFileName, m_androidConfig.getSdkToolsSha256())) {
|
|
|
|
|
logError(Tr::tr("Verifying the integrity of the downloaded file has failed."));
|
2023-06-22 12:42:43 +02:00
|
|
|
return SetupResult::StopWithError;
|
2023-06-07 11:15:12 +02:00
|
|
|
}
|
|
|
|
|
const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName);
|
|
|
|
|
if (!sourceAndCommand) {
|
|
|
|
|
logError(sourceAndCommand.error());
|
2023-06-22 12:42:43 +02:00
|
|
|
return SetupResult::StopWithError;
|
2023-06-07 11:15:12 +02:00
|
|
|
}
|
|
|
|
|
unarchiver.setSourceAndCommand(*sourceAndCommand);
|
|
|
|
|
unarchiver.setDestDir(sdkFileName.parentDir());
|
2023-06-22 12:42:43 +02:00
|
|
|
return SetupResult::Continue;
|
2023-06-07 11:15:12 +02:00
|
|
|
};
|
2023-11-03 14:42:55 +01:00
|
|
|
const auto onUnarchiverDone = [this, storage](DoneWith result) {
|
2023-11-03 09:42:55 +01:00
|
|
|
if (result != DoneWith::Success) {
|
2023-11-02 16:14:50 +01:00
|
|
|
logError(Tr::tr("Unarchiving error."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-07 11:15:12 +02:00
|
|
|
m_androidConfig.setTemporarySdkToolsPath(
|
|
|
|
|
(*storage)->parentDir().pathAppended(Constants::cmdlineToolsName));
|
|
|
|
|
QMetaObject::invokeMethod(this, [this] { emit sdkExtracted(); }, Qt::QueuedConnection);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Group root {
|
2023-11-19 14:18:41 +01:00
|
|
|
storage,
|
2023-11-02 16:14:50 +01:00
|
|
|
NetworkQueryTask(onQuerySetup, onQueryDone),
|
|
|
|
|
UnarchiverTask(onUnarchiveSetup, onUnarchiverDone)
|
2023-06-07 11:15:12 +02:00
|
|
|
};
|
|
|
|
|
|
2024-01-13 13:36:39 +01:00
|
|
|
m_taskTreeRunner.start(root);
|
2019-12-23 16:13:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString AndroidSdkDownloader::dialogTitle()
|
|
|
|
|
{
|
2022-10-06 17:53:35 +02:00
|
|
|
return Tr::tr("Download SDK Tools");
|
2019-12-23 16:13:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkDownloader::logError(const QString &error)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data());
|
2023-06-07 11:15:12 +02:00
|
|
|
QMetaObject::invokeMethod(this, [this, error] { emit sdkDownloaderError(error); },
|
|
|
|
|
Qt::QueuedConnection);
|
2019-12-23 16:13:23 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-07 10:47:13 +02:00
|
|
|
} // namespace Android::Internal
|