forked from qt-creator/qt-creator
It was no longer showing "No updates found." in the status bar if the
progress details are off, and the progress details were no longer
directly hidden if updates were found.
Amends 93401c9c81
Change-Id: I53c441a990c9cbda1a49969f57eb76128ab020c1
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
496 lines
18 KiB
C++
496 lines
18 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
#include "updateinfoplugin.h"
|
|
#include "updateinfotr.h"
|
|
|
|
#include "settingspage.h"
|
|
#include "updateinfotools.h"
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/coreconstants.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/progressmanager/futureprogress.h>
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
|
#include <coreplugin/settingsdatabase.h>
|
|
#include <utils/algorithm.h>
|
|
#include <utils/fileutils.h>
|
|
#include <utils/infobar.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/qtcprocess.h>
|
|
|
|
#include <QDate>
|
|
#include <QDomDocument>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QLabel>
|
|
#include <QLoggingCategory>
|
|
#include <QMenu>
|
|
#include <QMetaEnum>
|
|
#include <QPointer>
|
|
#include <QProcessEnvironment>
|
|
#include <QTimer>
|
|
#include <QVersionNumber>
|
|
|
|
#include <memory>
|
|
|
|
Q_LOGGING_CATEGORY(updateLog, "qtc.updateinfo", QtWarningMsg)
|
|
|
|
const char UpdaterGroup[] = "Updater";
|
|
const char MaintenanceToolKey[] = "MaintenanceTool";
|
|
const char AutomaticCheckKey[] = "AutomaticCheck";
|
|
const char CheckForNewQtVersionsKey[] = "CheckForNewQtVersions";
|
|
const char CheckIntervalKey[] = "CheckUpdateInterval";
|
|
const char LastCheckDateKey[] = "LastCheckDate";
|
|
const char LastMaxQtVersionKey[] = "LastMaxQtVersion";
|
|
const quint32 OneMinute = 60000;
|
|
const quint32 OneHour = 3600000;
|
|
const char InstallUpdates[] = "UpdateInfo.InstallUpdates";
|
|
const char InstallQtUpdates[] = "UpdateInfo.InstallQtUpdates";
|
|
const char M_MAINTENANCE_TOOL[] = "QtCreator.Menu.Tools.MaintenanceTool";
|
|
|
|
using namespace Core;
|
|
using namespace Utils;
|
|
|
|
namespace UpdateInfo {
|
|
namespace Internal {
|
|
|
|
class UpdateInfoPluginPrivate
|
|
{
|
|
public:
|
|
QString m_maintenanceTool;
|
|
std::unique_ptr<QtcProcess> m_maintenanceToolProcess;
|
|
QPointer<FutureProgress> m_progress;
|
|
QString m_updateOutput;
|
|
QString m_packagesOutput;
|
|
QTimer *m_checkUpdatesTimer = nullptr;
|
|
struct Settings
|
|
{
|
|
bool automaticCheck = true;
|
|
UpdateInfoPlugin::CheckUpdateInterval checkInterval = UpdateInfoPlugin::WeeklyCheck;
|
|
bool checkForQtVersions = true;
|
|
};
|
|
Settings m_settings;
|
|
QDate m_lastCheckDate;
|
|
QVersionNumber m_lastMaxQtVersion;
|
|
};
|
|
|
|
UpdateInfoPlugin::UpdateInfoPlugin()
|
|
: d(new UpdateInfoPluginPrivate)
|
|
{
|
|
d->m_checkUpdatesTimer = new QTimer(this);
|
|
d->m_checkUpdatesTimer->setTimerType(Qt::VeryCoarseTimer);
|
|
d->m_checkUpdatesTimer->setInterval(OneHour);
|
|
connect(d->m_checkUpdatesTimer, &QTimer::timeout,
|
|
this, &UpdateInfoPlugin::doAutoCheckForUpdates);
|
|
}
|
|
|
|
UpdateInfoPlugin::~UpdateInfoPlugin()
|
|
{
|
|
stopCheckForUpdates();
|
|
if (!d->m_maintenanceTool.isEmpty())
|
|
saveSettings();
|
|
|
|
delete d;
|
|
}
|
|
|
|
void UpdateInfoPlugin::startAutoCheckForUpdates()
|
|
{
|
|
doAutoCheckForUpdates();
|
|
|
|
d->m_checkUpdatesTimer->start();
|
|
}
|
|
|
|
void UpdateInfoPlugin::stopAutoCheckForUpdates()
|
|
{
|
|
d->m_checkUpdatesTimer->stop();
|
|
}
|
|
|
|
void UpdateInfoPlugin::doAutoCheckForUpdates()
|
|
{
|
|
if (d->m_maintenanceToolProcess)
|
|
return; // update task is still running (might have been run manually just before)
|
|
|
|
if (nextCheckDate().isValid() && nextCheckDate() > QDate::currentDate())
|
|
return; // not a time for check yet
|
|
|
|
startCheckForUpdates();
|
|
}
|
|
|
|
void UpdateInfoPlugin::startCheckForUpdates()
|
|
{
|
|
if (d->m_maintenanceToolProcess)
|
|
return; // do not trigger while update task is already running
|
|
|
|
QFutureInterface<void> futureIf;
|
|
d->m_progress = ProgressManager::addTimedTask(futureIf,
|
|
tr("Checking for Updates"),
|
|
Id("UpdateInfo.CheckingForUpdates"),
|
|
60);
|
|
d->m_progress->setKeepOnFinish(FutureProgress::KeepOnFinishTillUserInteraction);
|
|
d->m_progress->setSubtitleVisibleInStatusBar(true);
|
|
connect(d->m_progress, &FutureProgress::canceled, this, [this, futureIf]() mutable {
|
|
futureIf.reportCanceled();
|
|
futureIf.reportFinished();
|
|
stopCheckForUpdates();
|
|
});
|
|
|
|
d->m_maintenanceToolProcess.reset(new QtcProcess);
|
|
d->m_maintenanceToolProcess->setCommand({Utils::FilePath::fromString(d->m_maintenanceTool),
|
|
{"ch", "-g", "*=false,ifw.package.*=true"}});
|
|
d->m_maintenanceToolProcess->setTimeoutS(3 * 60); // 3 minutes
|
|
// TODO handle error
|
|
connect(
|
|
d->m_maintenanceToolProcess.get(),
|
|
&QtcProcess::done,
|
|
this,
|
|
[this, futureIf]() mutable {
|
|
if (d->m_maintenanceToolProcess->result() == ProcessResult::FinishedWithSuccess) {
|
|
d->m_updateOutput = d->m_maintenanceToolProcess->cleanedStdOut();
|
|
if (d->m_settings.checkForQtVersions) {
|
|
d->m_maintenanceToolProcess.reset(new QtcProcess);
|
|
d->m_maintenanceToolProcess->setCommand(
|
|
{Utils::FilePath::fromString(d->m_maintenanceTool),
|
|
{"se", "qt[.]qt[0-9][.][0-9]+$", "-g", "*=false,ifw.package.*=true"}});
|
|
d->m_maintenanceToolProcess->setTimeoutS(3 * 60); // 3 minutes
|
|
connect(
|
|
d->m_maintenanceToolProcess.get(),
|
|
&QtcProcess::done,
|
|
this,
|
|
[this, futureIf]() mutable {
|
|
if (d->m_maintenanceToolProcess->result()
|
|
== ProcessResult::FinishedWithSuccess) {
|
|
d->m_packagesOutput = d->m_maintenanceToolProcess->cleanedStdOut();
|
|
d->m_maintenanceToolProcess.reset();
|
|
futureIf.reportFinished();
|
|
checkForUpdatesFinished();
|
|
} else {
|
|
futureIf.reportCanceled(); // is used to indicate error
|
|
futureIf.reportFinished();
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
d->m_maintenanceToolProcess->start();
|
|
} else {
|
|
d->m_maintenanceToolProcess.reset();
|
|
futureIf.reportFinished();
|
|
checkForUpdatesFinished();
|
|
}
|
|
} else {
|
|
futureIf.reportCanceled(); // is used to indicate error
|
|
futureIf.reportFinished();
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
d->m_maintenanceToolProcess->start();
|
|
futureIf.reportStarted();
|
|
|
|
emit checkForUpdatesRunningChanged(true);
|
|
}
|
|
|
|
void UpdateInfoPlugin::stopCheckForUpdates()
|
|
{
|
|
if (!d->m_maintenanceToolProcess)
|
|
return;
|
|
|
|
d->m_maintenanceToolProcess->disconnect();
|
|
d->m_maintenanceToolProcess.reset();
|
|
d->m_updateOutput.clear();
|
|
d->m_packagesOutput.clear();
|
|
emit checkForUpdatesRunningChanged(false);
|
|
}
|
|
|
|
static void showUpdateInfo(const QList<Update> &updates, const std::function<void()> &startUpdater)
|
|
{
|
|
Utils::InfoBarEntry info(InstallUpdates,
|
|
UpdateInfoPlugin::tr("New updates are available. Start the update?"));
|
|
info.addCustomButton(UpdateInfoPlugin::tr("Start Update"), [startUpdater] {
|
|
Core::ICore::infoBar()->removeInfo(InstallUpdates);
|
|
startUpdater();
|
|
});
|
|
info.setDetailsWidgetCreator([updates]() -> QWidget * {
|
|
const QString updateText = Utils::transform(updates, [](const Update &u) {
|
|
return u.version.isEmpty()
|
|
? u.name
|
|
: UpdateInfoPlugin::tr("%1 (%2)",
|
|
"Package name and version")
|
|
.arg(u.name, u.version);
|
|
}).join("</li><li>");
|
|
auto label = new QLabel;
|
|
label->setText("<qt><p>" + UpdateInfoPlugin::tr("Available updates:") + "<ul><li>"
|
|
+ updateText + "</li></ul></p></qt>");
|
|
label->setContentsMargins(0, 0, 0, 8);
|
|
return label;
|
|
});
|
|
Core::ICore::infoBar()->removeInfo(InstallUpdates); // remove any existing notifications
|
|
Core::ICore::infoBar()->unsuppressInfo(InstallUpdates);
|
|
Core::ICore::infoBar()->addInfo(info);
|
|
}
|
|
|
|
static void showQtUpdateInfo(const QtPackage &package,
|
|
const std::function<void()> &startPackageManager)
|
|
{
|
|
Utils::InfoBarEntry info(InstallQtUpdates,
|
|
UpdateInfoPlugin::tr(
|
|
"%1 is available. Check the <a %2>Qt blog</a> for details.")
|
|
.arg(package.displayName,
|
|
QString("href=\"https://www.qt.io/blog/tag/releases\"")));
|
|
info.addCustomButton(UpdateInfoPlugin::tr("Start Package Manager"), [startPackageManager] {
|
|
Core::ICore::infoBar()->removeInfo(InstallQtUpdates);
|
|
startPackageManager();
|
|
});
|
|
info.addCustomButton(UpdateInfoPlugin::tr("Open Settings"), [] {
|
|
Core::ICore::infoBar()->removeInfo(InstallQtUpdates);
|
|
Core::ICore::showOptionsDialog(FILTER_OPTIONS_PAGE_ID);
|
|
});
|
|
Core::ICore::infoBar()->removeInfo(InstallQtUpdates); // remove any existing notifications
|
|
Core::ICore::infoBar()->unsuppressInfo(InstallQtUpdates);
|
|
Core::ICore::infoBar()->addInfo(info);
|
|
}
|
|
|
|
void UpdateInfoPlugin::checkForUpdatesFinished()
|
|
{
|
|
setLastCheckDate(QDate::currentDate());
|
|
|
|
qCDebug(updateLog) << "--- MaintenanceTool output (updates):";
|
|
qCDebug(updateLog) << qPrintable(d->m_updateOutput);
|
|
qCDebug(updateLog) << "--- MaintenanceTool output (packages):";
|
|
qCDebug(updateLog) << qPrintable(d->m_packagesOutput);
|
|
|
|
stopCheckForUpdates();
|
|
|
|
const QList<Update> updates = availableUpdates(d->m_updateOutput);
|
|
const QList<QtPackage> qtPackages = availableQtPackages(d->m_packagesOutput);
|
|
if (updateLog().isDebugEnabled()) {
|
|
qCDebug(updateLog) << "--- Available updates:";
|
|
for (const Update &u : updates)
|
|
qCDebug(updateLog) << u.name << u.version;
|
|
qCDebug(updateLog) << "--- Available Qt packages:";
|
|
for (const QtPackage &p : qtPackages) {
|
|
qCDebug(updateLog) << p.displayName << p.version << "installed:" << p.installed
|
|
<< "prerelease:" << p.isPrerelease;
|
|
}
|
|
}
|
|
std::optional<QtPackage> qtToNag = qtToNagAbout(qtPackages, &d->m_lastMaxQtVersion);
|
|
|
|
if (!updates.isEmpty() || qtToNag) {
|
|
// progress details are shown until user interaction for the "no updates" case,
|
|
// so we can show the "No updates found" text, but if we have updates we don't
|
|
// want to keep it around
|
|
if (d->m_progress)
|
|
d->m_progress->setKeepOnFinish(FutureProgress::HideOnFinish);
|
|
emit newUpdatesAvailable(true);
|
|
if (!updates.isEmpty())
|
|
showUpdateInfo(updates, [this] { startUpdater(); });
|
|
if (qtToNag)
|
|
showQtUpdateInfo(*qtToNag, [this] { startPackageManager(); });
|
|
} else {
|
|
emit newUpdatesAvailable(false);
|
|
if (d->m_progress)
|
|
d->m_progress->setSubtitle(tr("No updates found."));
|
|
}
|
|
}
|
|
|
|
bool UpdateInfoPlugin::isCheckForUpdatesRunning() const
|
|
{
|
|
return d->m_maintenanceToolProcess.get() != nullptr;
|
|
}
|
|
|
|
void UpdateInfoPlugin::extensionsInitialized()
|
|
{
|
|
if (isAutomaticCheck())
|
|
QTimer::singleShot(OneMinute, this, &UpdateInfoPlugin::startAutoCheckForUpdates);
|
|
}
|
|
|
|
bool UpdateInfoPlugin::initialize(const QStringList & /* arguments */, QString *errorMessage)
|
|
{
|
|
loadSettings();
|
|
|
|
if (d->m_maintenanceTool.isEmpty()) {
|
|
*errorMessage = tr("Could not determine location of maintenance tool. Please check "
|
|
"your installation if you did not enable this plugin manually.");
|
|
return false;
|
|
}
|
|
|
|
if (!QFileInfo(d->m_maintenanceTool).isExecutable()) {
|
|
*errorMessage = tr("The maintenance tool at \"%1\" is not an executable. Check your installation.")
|
|
.arg(d->m_maintenanceTool);
|
|
d->m_maintenanceTool.clear();
|
|
return false;
|
|
}
|
|
|
|
connect(ICore::instance(), &ICore::saveSettingsRequested,
|
|
this, &UpdateInfoPlugin::saveSettings);
|
|
|
|
(void) new SettingsPage(this);
|
|
|
|
auto mtools = ActionManager::actionContainer(Constants::M_TOOLS);
|
|
ActionContainer *mmaintenanceTool = ActionManager::createMenu(M_MAINTENANCE_TOOL);
|
|
mmaintenanceTool->setOnAllDisabledBehavior(Core::ActionContainer::Hide);
|
|
mmaintenanceTool->menu()->setTitle(Tr::tr("Qt Maintenance Tool")); mtools->addMenu(mmaintenanceTool);
|
|
|
|
QAction *checkForUpdatesAction = new QAction(tr("Check for Updates"), this);
|
|
checkForUpdatesAction->setMenuRole(QAction::ApplicationSpecificRole);
|
|
Core::Command *checkForUpdatesCommand
|
|
= Core::ActionManager::registerAction(checkForUpdatesAction, "Updates.CheckForUpdates");
|
|
connect(checkForUpdatesAction, &QAction::triggered,
|
|
this, &UpdateInfoPlugin::startCheckForUpdates);
|
|
mmaintenanceTool->addAction(checkForUpdatesCommand);
|
|
|
|
QAction *startMaintenanceToolAction = new QAction(Tr::tr("Start Maintenance Tool"), this);
|
|
startMaintenanceToolAction->setMenuRole(QAction::ApplicationSpecificRole);
|
|
Core::Command *startMaintenanceToolCommand
|
|
= Core::ActionManager::registerAction(startMaintenanceToolAction,
|
|
"Updates.StartMaintenanceTool");
|
|
connect(startMaintenanceToolAction, &QAction::triggered, this, [this]() {
|
|
startMaintenanceTool({});
|
|
});
|
|
mmaintenanceTool->addAction(startMaintenanceToolCommand);
|
|
|
|
return true;
|
|
}
|
|
|
|
void UpdateInfoPlugin::loadSettings() const
|
|
{
|
|
UpdateInfoPluginPrivate::Settings def;
|
|
QSettings *settings = ICore::settings();
|
|
const QString updaterKey = QLatin1String(UpdaterGroup) + '/';
|
|
d->m_maintenanceTool = settings->value(updaterKey + MaintenanceToolKey).toString();
|
|
d->m_lastCheckDate = settings->value(updaterKey + LastCheckDateKey, QDate()).toDate();
|
|
d->m_settings.automaticCheck
|
|
= settings->value(updaterKey + AutomaticCheckKey, def.automaticCheck).toBool();
|
|
const QMetaObject *mo = metaObject();
|
|
const QMetaEnum me = mo->enumerator(mo->indexOfEnumerator(CheckIntervalKey));
|
|
if (QTC_GUARD(me.isValid())) {
|
|
const QString checkInterval = settings
|
|
->value(updaterKey + CheckIntervalKey,
|
|
me.valueToKey(def.checkInterval))
|
|
.toString();
|
|
bool ok = false;
|
|
const int newValue = me.keyToValue(checkInterval.toUtf8(), &ok);
|
|
if (ok)
|
|
d->m_settings.checkInterval = static_cast<CheckUpdateInterval>(newValue);
|
|
}
|
|
const QString lastMaxQtVersionString = settings->value(updaterKey + LastMaxQtVersionKey)
|
|
.toString();
|
|
d->m_lastMaxQtVersion = QVersionNumber::fromString(lastMaxQtVersionString);
|
|
d->m_settings.checkForQtVersions
|
|
= settings->value(updaterKey + CheckForNewQtVersionsKey, def.checkForQtVersions).toBool();
|
|
}
|
|
|
|
void UpdateInfoPlugin::saveSettings()
|
|
{
|
|
UpdateInfoPluginPrivate::Settings def;
|
|
Utils::QtcSettings *settings = ICore::settings();
|
|
settings->beginGroup(UpdaterGroup);
|
|
settings->setValueWithDefault(LastCheckDateKey, d->m_lastCheckDate, QDate());
|
|
settings->setValueWithDefault(AutomaticCheckKey,
|
|
d->m_settings.automaticCheck,
|
|
def.automaticCheck);
|
|
// Note: don't save MaintenanceToolKey on purpose! This setting may be set only by installer.
|
|
// If creator is run not from installed SDK, the setting can be manually created here:
|
|
// [CREATOR_INSTALLATION_LOCATION]/share/qtcreator/QtProject/QtCreator.ini or
|
|
// [CREATOR_INSTALLATION_LOCATION]/Qt Creator.app/Contents/Resources/QtProject/QtCreator.ini on OS X
|
|
const QMetaObject *mo = metaObject();
|
|
const QMetaEnum me = mo->enumerator(mo->indexOfEnumerator(CheckIntervalKey));
|
|
settings->setValueWithDefault(CheckIntervalKey,
|
|
QString::fromUtf8(me.valueToKey(d->m_settings.checkInterval)),
|
|
QString::fromUtf8(me.valueToKey(def.checkInterval)));
|
|
settings->setValueWithDefault(LastMaxQtVersionKey, d->m_lastMaxQtVersion.toString());
|
|
settings->setValueWithDefault(CheckForNewQtVersionsKey,
|
|
d->m_settings.checkForQtVersions,
|
|
def.checkForQtVersions);
|
|
settings->endGroup();
|
|
}
|
|
|
|
bool UpdateInfoPlugin::isAutomaticCheck() const
|
|
{
|
|
return d->m_settings.automaticCheck;
|
|
}
|
|
|
|
void UpdateInfoPlugin::setAutomaticCheck(bool on)
|
|
{
|
|
if (d->m_settings.automaticCheck == on)
|
|
return;
|
|
|
|
d->m_settings.automaticCheck = on;
|
|
if (on)
|
|
startAutoCheckForUpdates();
|
|
else
|
|
stopAutoCheckForUpdates();
|
|
}
|
|
|
|
UpdateInfoPlugin::CheckUpdateInterval UpdateInfoPlugin::checkUpdateInterval() const
|
|
{
|
|
return d->m_settings.checkInterval;
|
|
}
|
|
|
|
void UpdateInfoPlugin::setCheckUpdateInterval(UpdateInfoPlugin::CheckUpdateInterval interval)
|
|
{
|
|
if (d->m_settings.checkInterval == interval)
|
|
return;
|
|
|
|
d->m_settings.checkInterval = interval;
|
|
}
|
|
|
|
bool UpdateInfoPlugin::isCheckingForQtVersions() const
|
|
{
|
|
return d->m_settings.checkForQtVersions;
|
|
}
|
|
|
|
void UpdateInfoPlugin::setCheckingForQtVersions(bool on)
|
|
{
|
|
d->m_settings.checkForQtVersions = on;
|
|
}
|
|
|
|
QDate UpdateInfoPlugin::lastCheckDate() const
|
|
{
|
|
return d->m_lastCheckDate;
|
|
}
|
|
|
|
void UpdateInfoPlugin::setLastCheckDate(const QDate &date)
|
|
{
|
|
if (d->m_lastCheckDate == date)
|
|
return;
|
|
|
|
d->m_lastCheckDate = date;
|
|
emit lastCheckDateChanged(date);
|
|
}
|
|
|
|
QDate UpdateInfoPlugin::nextCheckDate() const
|
|
{
|
|
return nextCheckDate(d->m_settings.checkInterval);
|
|
}
|
|
|
|
QDate UpdateInfoPlugin::nextCheckDate(CheckUpdateInterval interval) const
|
|
{
|
|
if (!d->m_lastCheckDate.isValid())
|
|
return QDate();
|
|
|
|
if (interval == DailyCheck)
|
|
return d->m_lastCheckDate.addDays(1);
|
|
if (interval == WeeklyCheck)
|
|
return d->m_lastCheckDate.addDays(7);
|
|
return d->m_lastCheckDate.addMonths(1);
|
|
}
|
|
|
|
void UpdateInfoPlugin::startMaintenanceTool(const QStringList &args) const
|
|
{
|
|
QtcProcess::startDetached(CommandLine{FilePath::fromString(d->m_maintenanceTool), args});
|
|
}
|
|
|
|
void UpdateInfoPlugin::startUpdater() const
|
|
{
|
|
startMaintenanceTool({"--updater"});
|
|
}
|
|
|
|
void UpdateInfoPlugin::startPackageManager() const
|
|
{
|
|
startMaintenanceTool({"--start-package-manager"});
|
|
}
|
|
|
|
} //namespace Internal
|
|
} //namespace UpdateInfo
|