// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "androidconfigurations.h" #include "androidconstants.h" #include "androidsdkdownloader.h" #include "androidtr.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Tasking; using namespace Utils; namespace { Q_LOGGING_CATEGORY(sdkDownloaderLog, "qtc.android.sdkDownloader", QtWarningMsg) } namespace Android::Internal { static void logError(const QString &error) { qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data()); QMessageBox::warning(Core::ICore::dialogParent(), dialogTitle(), error); } static bool isHttpRedirect(QNetworkReply *reply) { const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || statusCode == 308; } static FilePath sdkFromUrl(const QUrl &url) { const QString path = url.path(); QString basename = QFileInfo(path).fileName(); if (basename.isEmpty()) basename = "sdk-tools.zip"; if (QFileInfo::exists(basename)) { int i = 0; basename += '.'; while (QFileInfo::exists(basename + QString::number(i))) ++i; basename += QString::number(i); } return FilePath::fromString(QStandardPaths::writableLocation(QStandardPaths::TempLocation)) / basename; } // TODO: Make it a separate async task in a chain? static std::optional saveToDisk(const FilePath &filename, QIODevice *data) { QFile file(filename.toString()); if (!file.open(QIODevice::WriteOnly)) { return Tr::tr("Could not open \"%1\" for writing: %2.") .arg(filename.toUserOutput(), file.errorString()); } file.write(data->readAll()); return {}; } static void validateFileIntegrity(QPromise &promise, const FilePath &fileName, const QByteArray &sha256) { QFile file(fileName.toString()); if (file.open(QFile::ReadOnly)) { QCryptographicHash hash(QCryptographicHash::Sha256); if (hash.addData(&file) && hash.result() == sha256) return; } promise.future().cancel(); } GroupItem downloadSdkRecipe() { struct StorageStruct { StorageStruct() { progressDialog.reset(new QProgressDialog(Tr::tr("Downloading SDK Tools package..."), Tr::tr("Cancel"), 0, 100, Core::ICore::dialogParent())); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setWindowTitle(dialogTitle()); progressDialog->setFixedSize(progressDialog->sizeHint()); progressDialog->setAutoClose(false); progressDialog->show(); // TODO: Should not be needed. Investigate possible QT_BUG } std::unique_ptr progressDialog; std::optional sdkFileName; }; Storage storage; const auto onSetup = [] { if (AndroidConfig::sdkToolsUrl().isEmpty()) { logError(Tr::tr("The SDK Tools download URL is empty.")); return SetupResult::StopWithError; } return SetupResult::Continue; }; const auto onQuerySetup = [storage](NetworkQuery &query) { query.setRequest(QNetworkRequest(AndroidConfig::sdkToolsUrl())); query.setNetworkAccessManager(NetworkAccessManager::instance()); NetworkQuery *queryPtr = &query; QProgressDialog *progressDialog = storage->progressDialog.get(); QObject::connect(queryPtr, &NetworkQuery::started, progressDialog, [queryPtr, progressDialog] { QNetworkReply *reply = queryPtr->reply(); if (!reply) return; QObject::connect(reply, &QNetworkReply::downloadProgress, progressDialog, [progressDialog](qint64 received, qint64 max) { progressDialog->setRange(0, max); progressDialog->setValue(received); }); #if QT_CONFIG(ssl) QObject::connect(reply, &QNetworkReply::sslErrors, reply, [reply](const QList &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 = [storage](const NetworkQuery &query, DoneWith result) { if (result == DoneWith::Cancel) return; QNetworkReply *reply = query.reply(); QTC_ASSERT(reply, return); const QUrl url = reply->url(); if (result != DoneWith::Success) { logError(Tr::tr("Downloading Android SDK Tools from URL %1 has failed: %2.") .arg(url.toString(), reply->errorString())); return; } if (isHttpRedirect(reply)) { logError(Tr::tr("Download from %1 was redirected.").arg(url.toString())); return; } const FilePath sdkFileName = sdkFromUrl(url); const std::optional saveResult = saveToDisk(sdkFileName, reply); if (saveResult) { logError(*saveResult); return; } storage->sdkFileName = sdkFileName; }; const auto onValidationSetup = [storage](Async &async) { if (!storage->sdkFileName) return SetupResult::StopWithError; async.setConcurrentCallData(validateFileIntegrity, *storage->sdkFileName, AndroidConfig::getSdkToolsSha256()); storage->progressDialog->setRange(0, 0); storage->progressDialog->setLabelText(Tr::tr("Verifying package integrity...")); return SetupResult::Continue; }; const auto onValidationDone = [](DoneWith result) { if (result != DoneWith::Error) return; logError(Tr::tr("Verifying the integrity of the downloaded file has failed.")); }; const auto onUnarchiveSetup = [storage](Unarchiver &unarchiver) { storage->progressDialog->setRange(0, 0); storage->progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package...")); const FilePath sdkFileName = *storage->sdkFileName; const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName); if (!sourceAndCommand) { logError(sourceAndCommand.error()); return SetupResult::StopWithError; } unarchiver.setSourceAndCommand(*sourceAndCommand); unarchiver.setDestDir(sdkFileName.parentDir()); return SetupResult::Continue; }; const auto onUnarchiverDone = [storage](DoneWith result) { if (result == DoneWith::Cancel) return; if (result != DoneWith::Success) { logError(Tr::tr("Unarchiving error.")); return; } AndroidConfig::setTemporarySdkToolsPath( storage->sdkFileName->parentDir().pathAppended(Constants::cmdlineToolsName)); }; const auto onCancelSetup = [storage] { return std::make_pair(storage->progressDialog.get(), &QProgressDialog::canceled); }; return Group { storage, Group { onGroupSetup(onSetup), NetworkQueryTask(onQuerySetup, onQueryDone), AsyncTask(onValidationSetup, onValidationDone), UnarchiverTask(onUnarchiveSetup, onUnarchiverDone) }.withCancel(onCancelSetup) }; } QString dialogTitle() { return Tr::tr("Download SDK Tools"); } } // namespace Android::Internal