forked from qt-creator/qt-creator
Andriod: Check Android SDK pending licenses
Pending licenses are checked before installing any package and the user is provided with an option to agree to license terms and conditions Task-number: QTCREATORBUG-19000 Change-Id: I4e149b4a6ac84c1f336bb7c50b0d62a2019c7868 Reviewed-by: BogDan Vatra <bogdan@kdab.com> Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
@@ -32,9 +32,11 @@
|
|||||||
#include "utils/qtcassert.h"
|
#include "utils/qtcassert.h"
|
||||||
#include "utils/runextensions.h"
|
#include "utils/runextensions.h"
|
||||||
#include "utils/synchronousprocess.h"
|
#include "utils/synchronousprocess.h"
|
||||||
|
#include "utils/qtcprocess.h"
|
||||||
|
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
#include <QReadWriteLock>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
@@ -57,6 +59,10 @@ const char commonArgsKey[] = "Common Arguments:";
|
|||||||
const int sdkManagerCmdTimeoutS = 60;
|
const int sdkManagerCmdTimeoutS = 60;
|
||||||
const int sdkManagerOperationTimeoutS = 600;
|
const int sdkManagerOperationTimeoutS = 600;
|
||||||
|
|
||||||
|
const QRegularExpression assertionReg("(\\(\\s*y\\s*[\\/\\\\]\\s*n\\s*\\)\\s*)(?<mark>[\\:\\?])",
|
||||||
|
QRegularExpression::CaseInsensitiveOption |
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
|
using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
|
||||||
|
|
||||||
@@ -88,7 +94,7 @@ static bool valueForKey(QString key, const QString &line, QString *value = nullp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseProgress(const QString &out)
|
int parseProgress(const QString &out, bool &foundAssertion)
|
||||||
{
|
{
|
||||||
int progress = -1;
|
int progress = -1;
|
||||||
if (out.isEmpty())
|
if (out.isEmpty())
|
||||||
@@ -102,6 +108,8 @@ int parseProgress(const QString &out)
|
|||||||
if (progress < 0 || progress > 100)
|
if (progress < 0 || progress > 100)
|
||||||
progress = -1;
|
progress = -1;
|
||||||
}
|
}
|
||||||
|
if (!foundAssertion)
|
||||||
|
foundAssertion = assertionReg.match(line).hasMatch();
|
||||||
}
|
}
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
@@ -146,12 +154,15 @@ static void sdkManagerCommand(const Utils::FileName &toolPath, const QStringList
|
|||||||
{
|
{
|
||||||
int offset = fi.progressValue();
|
int offset = fi.progressValue();
|
||||||
SynchronousProcess proc;
|
SynchronousProcess proc;
|
||||||
|
bool assertionFound = false;
|
||||||
proc.setStdErrBufferedSignalsEnabled(true);
|
proc.setStdErrBufferedSignalsEnabled(true);
|
||||||
proc.setStdOutBufferedSignalsEnabled(true);
|
proc.setStdOutBufferedSignalsEnabled(true);
|
||||||
proc.setTimeoutS(timeout);
|
proc.setTimeoutS(timeout);
|
||||||
QObject::connect(&proc, &SynchronousProcess::stdOutBuffered,
|
QObject::connect(&proc, &SynchronousProcess::stdOutBuffered,
|
||||||
[offset, progressQuota, &fi](const QString &out) {
|
[offset, progressQuota, &proc, &assertionFound, &fi](const QString &out) {
|
||||||
int progressPercent = parseProgress(out);
|
int progressPercent = parseProgress(out, assertionFound);
|
||||||
|
if (assertionFound)
|
||||||
|
proc.terminate();
|
||||||
if (progressPercent != -1)
|
if (progressPercent != -1)
|
||||||
fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
|
fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
|
||||||
});
|
});
|
||||||
@@ -163,7 +174,15 @@ static void sdkManagerCommand(const Utils::FileName &toolPath, const QStringList
|
|||||||
&proc, &SynchronousProcess::terminate);
|
&proc, &SynchronousProcess::terminate);
|
||||||
}
|
}
|
||||||
SynchronousProcessResponse response = proc.run(toolPath.toString(), args);
|
SynchronousProcessResponse response = proc.run(toolPath.toString(), args);
|
||||||
output.success = response.result == SynchronousProcessResponse::Finished;
|
if (assertionFound) {
|
||||||
|
output.success = false;
|
||||||
|
output.stdOutput = response.stdOut();
|
||||||
|
output.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager",
|
||||||
|
"Operation requires user interaction."
|
||||||
|
"Please use \"sdkmanager\" commandline tool");
|
||||||
|
} else {
|
||||||
|
output.success = response.result == SynchronousProcessResponse::Finished;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -183,19 +202,29 @@ public:
|
|||||||
void updateInstalled(SdkCmdFutureInterface &fi);
|
void updateInstalled(SdkCmdFutureInterface &fi);
|
||||||
void update(SdkCmdFutureInterface &fi, const QStringList &install,
|
void update(SdkCmdFutureInterface &fi, const QStringList &install,
|
||||||
const QStringList &uninstall);
|
const QStringList &uninstall);
|
||||||
|
void checkPendingLicense(SdkCmdFutureInterface &fi);
|
||||||
|
void getPendingLicense(SdkCmdFutureInterface &fi);
|
||||||
|
|
||||||
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
||||||
|
void setLicenseInput(bool acceptLicense);
|
||||||
|
|
||||||
std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
|
std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QByteArray getUserInput() const;
|
||||||
|
void clearUserInput();
|
||||||
void reloadSdkPackages();
|
void reloadSdkPackages();
|
||||||
void clearPackages();
|
void clearPackages();
|
||||||
|
bool onLicenseStdOut(const QString &output, bool notify,
|
||||||
|
AndroidSdkManager::OperationOutput &result, SdkCmdFutureInterface &fi);
|
||||||
|
|
||||||
AndroidSdkManager &m_sdkManager;
|
AndroidSdkManager &m_sdkManager;
|
||||||
const AndroidConfig &m_config;
|
const AndroidConfig &m_config;
|
||||||
AndroidSdkPackageList m_allPackages;
|
AndroidSdkPackageList m_allPackages;
|
||||||
FileName lastSdkManagerPath;
|
FileName lastSdkManagerPath;
|
||||||
|
QString m_licenseTextCache;
|
||||||
|
QByteArray m_licenseUserInput;
|
||||||
|
mutable QReadWriteLock m_licenseInputLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -362,12 +391,35 @@ AndroidSdkManager::update(const QStringList &install, const QStringList &uninsta
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::checkPendingLicenses()
|
||||||
|
{
|
||||||
|
if (isBusy())
|
||||||
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
||||||
|
auto future = Utils::runAsync(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get());
|
||||||
|
m_d->addWatcher(future);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::runLicenseCommand()
|
||||||
|
{
|
||||||
|
if (isBusy())
|
||||||
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
||||||
|
auto future = Utils::runAsync(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get());
|
||||||
|
m_d->addWatcher(future);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidSdkManager::cancelOperatons()
|
void AndroidSdkManager::cancelOperatons()
|
||||||
{
|
{
|
||||||
emit cancelActiveOperations();
|
emit cancelActiveOperations();
|
||||||
m_d->m_activeOperation.reset();
|
m_d->m_activeOperation.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManager::acceptSdkLicense(bool accept)
|
||||||
|
{
|
||||||
|
m_d->setLicenseInput(accept);
|
||||||
|
}
|
||||||
|
|
||||||
void SdkManagerOutputParser::parsePackageListing(const QString &output)
|
void SdkManagerOutputParser::parsePackageListing(const QString &output)
|
||||||
{
|
{
|
||||||
QStringList packageData;
|
QStringList packageData;
|
||||||
@@ -809,6 +861,120 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi
|
|||||||
fi.setProgressValue(100);
|
fi.setProgressValue(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi)
|
||||||
|
{
|
||||||
|
fi.setProgressRange(0, 100);
|
||||||
|
fi.setProgressValue(0);
|
||||||
|
AndroidSdkManager::OperationOutput result;
|
||||||
|
result.type = AndroidSdkManager::LicenseCheck;
|
||||||
|
QStringList args("--licenses");
|
||||||
|
if (!fi.isCanceled())
|
||||||
|
sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result, 100.0);
|
||||||
|
else
|
||||||
|
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
|
||||||
|
|
||||||
|
fi.reportResult(result);
|
||||||
|
fi.setProgressValue(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi)
|
||||||
|
{
|
||||||
|
fi.setProgressRange(0, 100);
|
||||||
|
fi.setProgressValue(0);
|
||||||
|
AndroidSdkManager::OperationOutput result;
|
||||||
|
result.type = AndroidSdkManager::LicenseWorkflow;
|
||||||
|
QtcProcess licenseCommand;
|
||||||
|
bool reviewingLicenses = false;
|
||||||
|
licenseCommand.setCommand(m_config.sdkManagerToolPath().toString(), {"--licenses"});
|
||||||
|
if (Utils::HostOsInfo::isWindowsHost())
|
||||||
|
licenseCommand.setUseCtrlCStub(true);
|
||||||
|
licenseCommand.start();
|
||||||
|
QTextCodec *codec = QTextCodec::codecForLocale();
|
||||||
|
int inputCounter = 0, steps = -1;
|
||||||
|
while (!licenseCommand.waitForFinished(200)) {
|
||||||
|
QString stdOut = codec->toUnicode(licenseCommand.readAllStandardOutput());
|
||||||
|
bool assertionFound = false;
|
||||||
|
if (!stdOut.isEmpty())
|
||||||
|
assertionFound = onLicenseStdOut(stdOut, reviewingLicenses, result, fi);
|
||||||
|
|
||||||
|
if (reviewingLicenses) {
|
||||||
|
// Check user input
|
||||||
|
QByteArray userInput = getUserInput();
|
||||||
|
if (!userInput.isEmpty()) {
|
||||||
|
clearUserInput();
|
||||||
|
licenseCommand.write(userInput);
|
||||||
|
++inputCounter;
|
||||||
|
if (steps != -1)
|
||||||
|
fi.setProgressValue(qRound((inputCounter / (double)steps) * 100));
|
||||||
|
}
|
||||||
|
} else if (assertionFound) {
|
||||||
|
// The first assertion is to start reviewing licenses. Always accept.
|
||||||
|
reviewingLicenses = true;
|
||||||
|
QRegularExpression reg("(\\d+\\sof\\s)(?<steps>\\d+)");
|
||||||
|
QRegularExpressionMatch match = reg.match(stdOut);
|
||||||
|
if (match.hasMatch())
|
||||||
|
steps = match.captured("steps").toInt();
|
||||||
|
licenseCommand.write("Y\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fi.isCanceled()) {
|
||||||
|
licenseCommand.terminate();
|
||||||
|
if (!licenseCommand.waitForFinished(300)) {
|
||||||
|
licenseCommand.kill();
|
||||||
|
licenseCommand.waitForFinished(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (licenseCommand.state() == QProcess::NotRunning)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_licenseTextCache.clear();
|
||||||
|
result.success = licenseCommand.exitStatus() == QtcProcess::NormalExit;
|
||||||
|
if (!result.success) {
|
||||||
|
result.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager",
|
||||||
|
"License command failed.\n\n");
|
||||||
|
}
|
||||||
|
fi.reportResult(result);
|
||||||
|
fi.setProgressValue(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerPrivate::setLicenseInput(bool acceptLicense)
|
||||||
|
{
|
||||||
|
QWriteLocker locker(&m_licenseInputLock);
|
||||||
|
m_licenseUserInput = acceptLicense ? "Y\n" : "n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray AndroidSdkManagerPrivate::getUserInput() const
|
||||||
|
{
|
||||||
|
QReadLocker locker(&m_licenseInputLock);
|
||||||
|
return m_licenseUserInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerPrivate::clearUserInput()
|
||||||
|
{
|
||||||
|
QWriteLocker locker(&m_licenseInputLock);
|
||||||
|
m_licenseUserInput.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify,
|
||||||
|
AndroidSdkManager::OperationOutput &result,
|
||||||
|
SdkCmdFutureInterface &fi)
|
||||||
|
{
|
||||||
|
m_licenseTextCache.append(output);
|
||||||
|
QRegularExpressionMatch assertionMatch = assertionReg.match(m_licenseTextCache);
|
||||||
|
if (assertionMatch.hasMatch()) {
|
||||||
|
if (notify) {
|
||||||
|
result.stdOutput = m_licenseTextCache;
|
||||||
|
fi.reportResult(result);
|
||||||
|
}
|
||||||
|
// Clear the current contents. The found license text is dispatched. Continue collecting the
|
||||||
|
// next license text.
|
||||||
|
m_licenseTextCache.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
|
void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
|
||||||
{
|
{
|
||||||
if (future.isFinished())
|
if (future.isFinished())
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ public:
|
|||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
UpdateAll,
|
UpdateAll,
|
||||||
UpdatePackage
|
UpdatePackage,
|
||||||
|
LicenseCheck,
|
||||||
|
LicenseWorkflow
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OperationOutput
|
struct OperationOutput
|
||||||
@@ -78,8 +80,11 @@ public:
|
|||||||
QFuture<QString> availableArguments() const;
|
QFuture<QString> availableArguments() const;
|
||||||
QFuture<OperationOutput> updateAll();
|
QFuture<OperationOutput> updateAll();
|
||||||
QFuture<OperationOutput> update(const QStringList &install, const QStringList &uninstall);
|
QFuture<OperationOutput> update(const QStringList &install, const QStringList &uninstall);
|
||||||
|
QFuture<OperationOutput> checkPendingLicenses();
|
||||||
|
QFuture<OperationOutput> runLicenseCommand();
|
||||||
|
|
||||||
void cancelOperatons();
|
void cancelOperatons();
|
||||||
|
void acceptSdkLicense(bool accept);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void packageReloadBegin();
|
void packageReloadBegin();
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ AndroidSdkManagerWidget::AndroidSdkManagerWidget(AndroidConfig &config,
|
|||||||
{
|
{
|
||||||
QTC_CHECK(sdkManager);
|
QTC_CHECK(sdkManager);
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
m_ui->sdkLicensebuttonBox->hide();
|
||||||
|
m_ui->sdkLicenseLabel->hide();
|
||||||
m_ui->warningLabel->setElideMode(Qt::ElideRight);
|
m_ui->warningLabel->setElideMode(Qt::ElideRight);
|
||||||
m_ui->warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap());
|
m_ui->warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap());
|
||||||
m_ui->viewStack->setCurrentWidget(m_ui->packagesStack);
|
m_ui->viewStack->setCurrentWidget(m_ui->packagesStack);
|
||||||
@@ -151,6 +153,14 @@ AndroidSdkManagerWidget::AndroidSdkManagerWidget(AndroidConfig &config,
|
|||||||
this, &AndroidSdkManagerWidget::onNativeSdkManager);
|
this, &AndroidSdkManagerWidget::onNativeSdkManager);
|
||||||
connect(m_ui->optionsButton, &QPushButton::clicked,
|
connect(m_ui->optionsButton, &QPushButton::clicked,
|
||||||
this, &AndroidSdkManagerWidget::onSdkManagerOptions);
|
this, &AndroidSdkManagerWidget::onSdkManagerOptions);
|
||||||
|
connect(m_ui->sdkLicensebuttonBox, &QDialogButtonBox::accepted, [this]() {
|
||||||
|
m_sdkManager->acceptSdkLicense(true);
|
||||||
|
m_ui->sdkLicensebuttonBox->setEnabled(false); // Wait for next license to enable controls
|
||||||
|
});
|
||||||
|
connect(m_ui->sdkLicensebuttonBox, &QDialogButtonBox::rejected, [this]() {
|
||||||
|
m_sdkManager->acceptSdkLicense(false);
|
||||||
|
m_ui->sdkLicensebuttonBox->setEnabled(false); // Wait for next license to enable controls
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidSdkManagerWidget::~AndroidSdkManagerWidget()
|
AndroidSdkManagerWidget::~AndroidSdkManagerWidget()
|
||||||
@@ -179,9 +189,15 @@ void AndroidSdkManagerWidget::installEssentials()
|
|||||||
m_ui->applySelectionButton->click();
|
m_ui->applySelectionButton->click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerWidget::beginLicenseCheck()
|
||||||
|
{
|
||||||
|
m_formatter->appendMessage(tr("Checking pending licenses...\n"), Utils::NormalMessageFormat);
|
||||||
|
addPackageFuture(m_sdkManager->checkPendingLicenses());
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::onApplyButton()
|
void AndroidSdkManagerWidget::onApplyButton()
|
||||||
{
|
{
|
||||||
QTC_ASSERT(currentView() == PackageListing, return);
|
QTC_ASSERT(m_currentView == PackageListing, return);
|
||||||
|
|
||||||
if (m_sdkManager->isBusy()) {
|
if (m_sdkManager->isBusy()) {
|
||||||
m_formatter->appendMessage(tr("\nSDK Manager is busy."), Utils::StdErrFormat);
|
m_formatter->appendMessage(tr("\nSDK Manager is busy."), Utils::StdErrFormat);
|
||||||
@@ -192,16 +208,13 @@ void AndroidSdkManagerWidget::onApplyButton()
|
|||||||
if (packagesToUpdate.isEmpty())
|
if (packagesToUpdate.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QStringList installPackages, uninstallPackages, installSdkPaths, uninstallSdkPaths;
|
QStringList installPackages, uninstallPackages;
|
||||||
for (auto package : packagesToUpdate) {
|
for (auto package : packagesToUpdate) {
|
||||||
QString str = QString(" %1").arg(package->descriptionText());
|
QString str = QString(" %1").arg(package->descriptionText());
|
||||||
if (package->state() == AndroidSdkPackage::Installed) {
|
if (package->state() == AndroidSdkPackage::Installed)
|
||||||
uninstallSdkPaths << package->sdkStylePath();
|
|
||||||
uninstallPackages << str;
|
uninstallPackages << str;
|
||||||
} else {
|
else
|
||||||
installSdkPaths << package->sdkStylePath();
|
|
||||||
installPackages << str;
|
installPackages << str;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox messageDlg(QMessageBox::Information, tr("Android SDK Changes"),
|
QMessageBox messageDlg(QMessageBox::Information, tr("Android SDK Changes"),
|
||||||
@@ -222,17 +235,16 @@ void AndroidSdkManagerWidget::onApplyButton()
|
|||||||
if (messageDlg.exec() == QMessageBox::Cancel)
|
if (messageDlg.exec() == QMessageBox::Cancel)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// User agreed with the selection. Begin packages install/uninstall
|
|
||||||
emit updatingSdk();
|
|
||||||
switchView(Operations);
|
switchView(Operations);
|
||||||
m_formatter->appendMessage(tr("Updating selected packages...\n"),
|
m_pendingCommand = AndroidSdkManager::UpdatePackage;
|
||||||
Utils::NormalMessageFormat);
|
// User agreed with the selection. Check for licenses.
|
||||||
m_formatter->appendMessage(tr("Closing the %1 dialog will cancel the running and scheduled SDK "
|
if (!installPackages.isEmpty()) {
|
||||||
"operations.\n").arg(Utils::HostOsInfo::isMacHost() ?
|
// Pending license affects installtion only.
|
||||||
tr("preferences") : tr("options")),
|
beginLicenseCheck();
|
||||||
Utils::LogMessageFormat);
|
} else {
|
||||||
|
// Uninstall only. Go Ahead.
|
||||||
addPackageFuture(m_sdkManager->update(installSdkPaths, uninstallSdkPaths));
|
beginExecution();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::onUpdatePackages()
|
void AndroidSdkManagerWidget::onUpdatePackages()
|
||||||
@@ -242,8 +254,8 @@ void AndroidSdkManagerWidget::onUpdatePackages()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switchView(Operations);
|
switchView(Operations);
|
||||||
m_formatter->appendMessage(tr("Updating installed packages\n"), Utils::NormalMessageFormat);
|
m_pendingCommand = AndroidSdkManager::UpdateAll;
|
||||||
addPackageFuture(m_sdkManager->updateAll());
|
beginLicenseCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::onCancel()
|
void AndroidSdkManagerWidget::onCancel()
|
||||||
@@ -267,12 +279,42 @@ void AndroidSdkManagerWidget::onNativeSdkManager()
|
|||||||
void AndroidSdkManagerWidget::onOperationResult(int index)
|
void AndroidSdkManagerWidget::onOperationResult(int index)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(m_currentOperation, return);
|
QTC_ASSERT(m_currentOperation, return);
|
||||||
auto breakLine = [](const QString &line) { return line.endsWith("\n") ? line : line + "\n";};
|
|
||||||
AndroidSdkManager::OperationOutput result = m_currentOperation->resultAt(index);
|
AndroidSdkManager::OperationOutput result = m_currentOperation->resultAt(index);
|
||||||
if (!result.stdError.isEmpty())
|
if (result.type == AndroidSdkManager::LicenseWorkflow) {
|
||||||
|
// Show license controls and enable to user input.
|
||||||
|
m_ui->sdkLicenseLabel->setVisible(true);
|
||||||
|
m_ui->sdkLicensebuttonBox->setVisible(true);
|
||||||
|
m_ui->sdkLicensebuttonBox->setEnabled(true);
|
||||||
|
m_ui->sdkLicensebuttonBox->button(QDialogButtonBox::No)->setDefault(true);
|
||||||
|
}
|
||||||
|
auto breakLine = [](const QString &line) { return line.endsWith("\n") ? line : line + "\n";};
|
||||||
|
if (!result.stdError.isEmpty() && result.type != AndroidSdkManager::LicenseCheck)
|
||||||
m_formatter->appendMessage(breakLine(result.stdError), Utils::StdErrFormat);
|
m_formatter->appendMessage(breakLine(result.stdError), Utils::StdErrFormat);
|
||||||
if (!result.stdOutput.isEmpty())
|
if (!result.stdOutput.isEmpty() && result.type != AndroidSdkManager::LicenseCheck)
|
||||||
m_formatter->appendMessage(breakLine(result.stdOutput), Utils::StdOutFormat);
|
m_formatter->appendMessage(breakLine(result.stdOutput), Utils::StdOutFormat);
|
||||||
|
m_ui->outputEdit->ensureCursorVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerWidget::onLicenseCheckResult(const AndroidSdkManager::OperationOutput& output)
|
||||||
|
{
|
||||||
|
if (output.success) {
|
||||||
|
// No assertion was found. Looks like all license are accepted. Go Ahead.
|
||||||
|
runPendingCommand();
|
||||||
|
} else {
|
||||||
|
// Assertion was found. Provide user workflow to accept licenses.
|
||||||
|
QString warningMessage = tr("\nPlease note that the installation and use of Android SDK "
|
||||||
|
"packages may fail if respective licenses are not accepted.");
|
||||||
|
int userSelection = QMessageBox::question(this, tr("Android SDK Licenses"),
|
||||||
|
output.stdOutput + warningMessage,
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (userSelection == QMessageBox::Yes) {
|
||||||
|
// Run license workflow.
|
||||||
|
beginLicenseWorkflow();
|
||||||
|
} else {
|
||||||
|
// User decided to go ahead anyways.
|
||||||
|
runPendingCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput>
|
void AndroidSdkManagerWidget::addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput>
|
||||||
@@ -299,33 +341,100 @@ void AndroidSdkManagerWidget::addPackageFuture(const QFuture<AndroidSdkManager::
|
|||||||
Utils::StdErrFormat);
|
Utils::StdErrFormat);
|
||||||
}
|
}
|
||||||
notifyOperationFinished();
|
notifyOperationFinished();
|
||||||
|
switchView(PackageListing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerWidget::beginExecution()
|
||||||
|
{
|
||||||
|
const QList<const AndroidSdkPackage *> packagesToUpdate = m_sdkModel->userSelection();
|
||||||
|
if (packagesToUpdate.isEmpty()) {
|
||||||
|
switchView(PackageListing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList installSdkPaths, uninstallSdkPaths;
|
||||||
|
for (auto package : packagesToUpdate) {
|
||||||
|
if (package->state() == AndroidSdkPackage::Installed)
|
||||||
|
uninstallSdkPaths << package->sdkStylePath();
|
||||||
|
else
|
||||||
|
installSdkPaths << package->sdkStylePath();
|
||||||
|
}
|
||||||
|
m_formatter->appendMessage(tr("Installing/Uninstalling selected packages...\n"),
|
||||||
|
Utils::NormalMessageFormat);
|
||||||
|
m_formatter->appendMessage(tr("Closing the %1 dialog will cancel the running and scheduled SDK "
|
||||||
|
"operations.\n").arg(Utils::HostOsInfo::isMacHost() ?
|
||||||
|
tr("preferences") : tr("options")),
|
||||||
|
Utils::LogMessageFormat);
|
||||||
|
|
||||||
|
addPackageFuture(m_sdkManager->update(installSdkPaths, uninstallSdkPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerWidget::beginUpdate()
|
||||||
|
{
|
||||||
|
m_formatter->appendMessage(tr("Updating installed packages...\n"), Utils::NormalMessageFormat);
|
||||||
|
m_formatter->appendMessage(tr("Closing the %1 dialog will cancel the running and scheduled SDK "
|
||||||
|
"operations.\n").arg(Utils::HostOsInfo::isMacHost() ?
|
||||||
|
tr("preferences") : tr("options")),
|
||||||
|
Utils::LogMessageFormat);
|
||||||
|
addPackageFuture(m_sdkManager->updateAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidSdkManagerWidget::beginLicenseWorkflow()
|
||||||
|
{
|
||||||
|
switchView(LicenseWorkflow);
|
||||||
|
addPackageFuture(m_sdkManager->runLicenseCommand());
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::notifyOperationFinished()
|
void AndroidSdkManagerWidget::notifyOperationFinished()
|
||||||
{
|
{
|
||||||
if (!m_currentOperation || m_currentOperation->isFinished()) {
|
if (!m_currentOperation || m_currentOperation->isFinished()) {
|
||||||
QMessageBox::information(this, tr("Android SDK Changes"),
|
QMessageBox::information(this, tr("Android SDK Changes"),
|
||||||
tr("Android SDK operations finished."), QMessageBox::Ok);
|
tr("Android SDK operations finished."), QMessageBox::Ok);
|
||||||
switchView(PackageListing);
|
|
||||||
m_ui->operationProgress->setValue(0);
|
m_ui->operationProgress->setValue(0);
|
||||||
m_sdkManager->reloadPackages(true);
|
|
||||||
emit updatingSdkFinished();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::packageFutureFinished()
|
void AndroidSdkManagerWidget::packageFutureFinished()
|
||||||
{
|
{
|
||||||
if (!m_currentOperation) {
|
QTC_ASSERT (m_currentOperation, return);
|
||||||
qCDebug(androidSdkMgrUiLog) << "Invalid State. No active operation.";
|
|
||||||
return;
|
bool continueWorkflow = true;
|
||||||
} else if (m_currentOperation->isCanceled()) {
|
if (m_currentOperation->isCanceled()) {
|
||||||
m_formatter->appendMessage(tr("Operation cancelled.\n"), Utils::StdErrFormat);
|
m_formatter->appendMessage(tr("Operation cancelled.\n"), Utils::StdErrFormat);
|
||||||
|
continueWorkflow = false;
|
||||||
}
|
}
|
||||||
m_ui->operationProgress->setValue(100);
|
m_ui->operationProgress->setValue(100);
|
||||||
m_currentOperation->deleteLater();
|
int resultCount = m_currentOperation->future().resultCount();
|
||||||
m_currentOperation = nullptr;
|
if (continueWorkflow && resultCount > 0) {
|
||||||
notifyOperationFinished();
|
AndroidSdkManager::OperationOutput output = m_currentOperation->resultAt(resultCount -1);
|
||||||
|
AndroidSdkManager::CommandType type = output.type;
|
||||||
|
m_currentOperation->deleteLater();
|
||||||
|
m_currentOperation = nullptr;
|
||||||
|
switch (type) {
|
||||||
|
case AndroidSdkManager::LicenseCheck:
|
||||||
|
onLicenseCheckResult(output);
|
||||||
|
break;
|
||||||
|
case AndroidSdkManager::LicenseWorkflow:
|
||||||
|
m_ui->sdkLicensebuttonBox->hide();
|
||||||
|
m_ui->sdkLicenseLabel->hide();
|
||||||
|
runPendingCommand();
|
||||||
|
break;
|
||||||
|
case AndroidSdkManager::UpdateAll:
|
||||||
|
case AndroidSdkManager::UpdatePackage:
|
||||||
|
notifyOperationFinished();
|
||||||
|
switchView(PackageListing);
|
||||||
|
m_sdkManager->reloadPackages(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_currentOperation->deleteLater();
|
||||||
|
m_currentOperation = nullptr;
|
||||||
|
switchView(PackageListing);
|
||||||
|
m_sdkManager->reloadPackages(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::cancelPendingOperations()
|
void AndroidSdkManagerWidget::cancelPendingOperations()
|
||||||
@@ -333,6 +442,7 @@ void AndroidSdkManagerWidget::cancelPendingOperations()
|
|||||||
if (!m_sdkManager->isBusy()) {
|
if (!m_sdkManager->isBusy()) {
|
||||||
m_formatter->appendMessage(tr("\nNo pending operations to cancel...\n"),
|
m_formatter->appendMessage(tr("\nNo pending operations to cancel...\n"),
|
||||||
Utils::NormalMessageFormat);
|
Utils::NormalMessageFormat);
|
||||||
|
switchView(PackageListing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_formatter->appendMessage(tr("\nCancelling pending operations...\n"),
|
m_formatter->appendMessage(tr("\nCancelling pending operations...\n"),
|
||||||
@@ -342,15 +452,29 @@ void AndroidSdkManagerWidget::cancelPendingOperations()
|
|||||||
|
|
||||||
void AndroidSdkManagerWidget::switchView(AndroidSdkManagerWidget::View view)
|
void AndroidSdkManagerWidget::switchView(AndroidSdkManagerWidget::View view)
|
||||||
{
|
{
|
||||||
m_ui->viewStack->setCurrentWidget(view == PackageListing ?
|
if (m_currentView == PackageListing) {
|
||||||
|
m_formatter->clear();
|
||||||
|
m_ui->outputEdit->clear();
|
||||||
|
}
|
||||||
|
m_currentView = view;
|
||||||
|
if (m_currentView == PackageListing)
|
||||||
|
emit updatingSdkFinished();
|
||||||
|
else
|
||||||
|
emit updatingSdk();
|
||||||
|
|
||||||
|
m_ui->operationProgress->setValue(0);
|
||||||
|
m_ui->viewStack->setCurrentWidget(m_currentView == PackageListing ?
|
||||||
m_ui->packagesStack : m_ui->outputStack);
|
m_ui->packagesStack : m_ui->outputStack);
|
||||||
m_formatter->clear();
|
|
||||||
m_ui->outputEdit->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidSdkManagerWidget::View AndroidSdkManagerWidget::currentView() const
|
void AndroidSdkManagerWidget::runPendingCommand()
|
||||||
{
|
{
|
||||||
return m_ui->viewStack->currentWidget() == m_ui->packagesStack ? PackageListing : Operations;
|
if (m_pendingCommand == AndroidSdkManager::UpdatePackage)
|
||||||
|
beginExecution(); // License workflow can only start when updating packages.
|
||||||
|
else if (m_pendingCommand == AndroidSdkManager::UpdateAll)
|
||||||
|
beginUpdate();
|
||||||
|
else
|
||||||
|
QTC_ASSERT(false, qCDebug(androidSdkMgrUiLog) << "Unexpected state: No pending command.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidSdkManagerWidget::onSdkManagerOptions()
|
void AndroidSdkManagerWidget::onSdkManagerOptions()
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class AndroidSdkManagerWidget : public QWidget
|
|||||||
|
|
||||||
enum View {
|
enum View {
|
||||||
PackageListing,
|
PackageListing,
|
||||||
Operations
|
Operations,
|
||||||
|
LicenseWorkflow
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -69,15 +70,22 @@ private:
|
|||||||
void onCancel();
|
void onCancel();
|
||||||
void onNativeSdkManager();
|
void onNativeSdkManager();
|
||||||
void onOperationResult(int index);
|
void onOperationResult(int index);
|
||||||
|
void onLicenseCheckResult(const AndroidSdkManager::OperationOutput &output);
|
||||||
void onSdkManagerOptions();
|
void onSdkManagerOptions();
|
||||||
void addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
void addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
||||||
|
void beginLicenseCheck();
|
||||||
|
void beginExecution();
|
||||||
|
void beginUpdate();
|
||||||
|
void beginLicenseWorkflow();
|
||||||
void notifyOperationFinished();
|
void notifyOperationFinished();
|
||||||
void packageFutureFinished();
|
void packageFutureFinished();
|
||||||
void cancelPendingOperations();
|
void cancelPendingOperations();
|
||||||
void switchView(View view);
|
void switchView(View view);
|
||||||
View currentView() const;
|
void runPendingCommand();
|
||||||
|
|
||||||
AndroidConfig &m_androidConfig;
|
AndroidConfig &m_androidConfig;
|
||||||
|
AndroidSdkManager::CommandType m_pendingCommand = AndroidSdkManager::None;
|
||||||
|
View m_currentView = PackageListing;
|
||||||
AndroidSdkManager *m_sdkManager = nullptr;
|
AndroidSdkManager *m_sdkManager = nullptr;
|
||||||
AndroidSdkModel *m_sdkModel = nullptr;
|
AndroidSdkModel *m_sdkModel = nullptr;
|
||||||
Ui::AndroidSdkManagerWidget *m_ui = nullptr;
|
Ui::AndroidSdkManagerWidget *m_ui = nullptr;
|
||||||
|
|||||||
@@ -211,34 +211,14 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="outputStack">
|
<widget class="QWidget" name="outputStack">
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
<item row="2" column="1">
|
<item row="3" column="2">
|
||||||
<widget class="QPushButton" name="cancelButton">
|
<widget class="QPushButton" name="cancelButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cancel</string>
|
<string>Cancel</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0" colspan="3">
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0" colspan="2">
|
|
||||||
<widget class="QPlainTextEdit" name="outputEdit">
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0" colspan="2">
|
|
||||||
<widget class="QProgressBar" name="operationProgress">
|
<widget class="QProgressBar" name="operationProgress">
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>100</number>
|
<number>100</number>
|
||||||
@@ -254,6 +234,58 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QPlainTextEdit" name="outputEdit">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="sdkLicenseLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Do you want to accept the Android SDK license?</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QDialogButtonBox" name="sdkLicensebuttonBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
Reference in New Issue
Block a user