// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "updateinfoplugin.h"
#include "updateinfoservice.h"
#include "updateinfosettings.h"
#include "updateinfotools.h"
#include "updateinfotr.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 M_MAINTENANCE_TOOL[] = "QtCreator.Menu.Tools.MaintenanceTool";
using namespace Core;
using namespace QtTaskTree;
using namespace Utils;
namespace UpdateInfo {
namespace Internal {
class ServiceImpl final : public QObject, public UpdateInfo::Service
{
Q_OBJECT
Q_INTERFACES(UpdateInfo::Service)
public:
bool installPackages(const QString &filterRegex) override;
};
class UpdateInfoPluginPrivate
{
public:
FilePath m_maintenanceTool;
QSingleTaskTreeRunner m_taskTreeRunner;
QPointer 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;
std::unique_ptr m_service;
};
static UpdateInfoPluginPrivate *m_d = nullptr;
bool ServiceImpl::installPackages(const QString &filterRegex)
{
QDialog dialog;
dialog.setWindowTitle(Tr::tr("Installing Packages"));
dialog.resize(500, 250);
auto buttons = new QDialogButtonBox;
QPushButton *cancelButton = buttons->addButton(QDialogButtonBox::Cancel);
QPushButton *actionButton = buttons->addButton(QDialogButtonBox::Ok);
actionButton->setText(Tr::tr("Install"));
actionButton->setEnabled(false);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
auto searchProgress = new QProgressBar;
searchProgress->setRange(0, 0);
auto installProgress = new QProgressBar;
installProgress->setRange(0, 0);
QStackedWidget *stackWidget = nullptr;
QWidget *notFoundPage = nullptr;
QWidget *alreadyInstalledPage = nullptr;
QWidget *packagesPage = nullptr;
auto packageList = new QLabel;
QWidget *installPage = nullptr;
auto installLabel = new QLabel(Tr::tr("Installing..."));
auto installDetails = new QTextEdit;
installDetails->setReadOnly(true);
// clang-format off
{
using namespace Layouting;
Column {
Stack {
bindTo(&stackWidget),
Widget {
Column {
Tr::tr("Searching for packages..."),
searchProgress,
st
}
},
Widget {
bindTo(&alreadyInstalledPage),
Column {
Label {
wordWrap(true),
textInteractionFlags(Qt::TextBrowserInteraction),
text(Tr::tr("All packages matching \"%1\" are already installed.")
.arg(filterRegex))
},
st
}
},
Widget {
bindTo(¬FoundPage),
Column {
Label {
wordWrap(true),
textInteractionFlags(Qt::TextBrowserInteraction),
text(Tr::tr("No packages matching \"%1\" were found.").arg(filterRegex))
},
st
}
},
Widget {
bindTo(&packagesPage),
Column {
Tr::tr("The following packages were found:"),
packageList,
st
}
},
Widget {
bindTo(&installPage),
Column {
installLabel,
installProgress,
installDetails
}
}
},
st,
buttons
}.attachTo(&dialog);
}
// clang-format on
QSingleTaskTreeRunner runner;
QList packages;
const auto startInstallation = [&dialog,
stackWidget,
installPage,
installLabel,
installProgress,
installDetails,
actionButton,
cancelButton,
&packages,
&runner] {
stackWidget->setCurrentWidget(installPage);
actionButton->setEnabled(false);
disconnect(actionButton, nullptr, &dialog, nullptr);
const auto onInstallSetup = [&packages, installDetails](Process &process) {
const QStringList packageIds = Utils::transform(packages, &Package::name);
const QStringList args = QStringList("in") + packageIds
+ QStringList{
"--default-answer",
"--accept-obligations",
"--confirm-command",
"--accept-licenses"};
process.setCommand({m_d->m_maintenanceTool, args});
process.setProcessChannelMode(QProcess::MergedChannels);
process.setStdOutLineCallback([installDetails](QString s) {
if (s.endsWith('\n'))
s.chop(1);
if (s.endsWith('\r'))
s.chop(1);
installDetails->append(s);
});
};
const auto onInstallDone =
[&dialog, installLabel, installProgress, actionButton, cancelButton](
const Process &process) {
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
installLabel->setText(
Tr::tr("An error occurred. Check the output of the installer below."));
installProgress->setRange(0, 100);
installProgress->setValue(50);
return;
}
installLabel->setText(Tr::tr("Done."));
installProgress->setRange(0, 100);
installProgress->setValue(100);
actionButton->setText(Tr::tr("Close"));
actionButton->setEnabled(true);
connect(actionButton, &QPushButton::clicked, &dialog, &QDialog::accept);
cancelButton->setVisible(false);
};
runner.start({ProcessTask(onInstallSetup, onInstallDone)});
};
const auto showNotFoundPage = [stackWidget, notFoundPage, cancelButton] {
stackWidget->setCurrentWidget(notFoundPage);
cancelButton->setText(Tr::tr("Close"));
};
const auto showAlreadyInstalledPage = [stackWidget, alreadyInstalledPage, cancelButton] {
stackWidget->setCurrentWidget(alreadyInstalledPage);
cancelButton->setText(Tr::tr("Close"));
};
const auto showPackagesPage = [&dialog,
stackWidget,
packagesPage,
packageList,
actionButton,
&packages,
startInstallation] {
packageList->setText(
Utils::transform(packages, [](const Package &p) {
//: %1 = package name, %2 = package version
return Tr::tr("%1 (Version: %2)").arg(p.displayName, p.version.toString());
}).join("
"));
stackWidget->setCurrentWidget(packagesPage);
actionButton->setEnabled(true);
connect(actionButton, &QPushButton::clicked, &dialog, [startInstallation] {
startInstallation();
});
};
const auto onSearchSetup = [filterRegex](Process &process) {
qCDebug(updateLog) << "Update service looking for packages matching" << filterRegex;
process.setCommand(
{m_d->m_maintenanceTool,
{"se", filterRegex, "--type=package", "-g", "*=false,ifw.package.*=true"}});
};
const auto onSearchDone = [&packages,
showNotFoundPage,
showAlreadyInstalledPage,
showPackagesPage](const Process &process) {
const QString cleanedStdOut = process.cleanedStdOut();
qCDebug(updateLog) << "MaintenanceTool output:";
qCDebug(updateLog) << cleanedStdOut;
const QList allAvailablePackages = availablePackages(cleanedStdOut);
packages = Utils::filtered(allAvailablePackages, [](const Package &p) {
return p.installedVersion.isNull();
});
if (allAvailablePackages.isEmpty())
showNotFoundPage();
else if (packages.isEmpty())
showAlreadyInstalledPage();
else
showPackagesPage();
};
runner.start({ProcessTask(onSearchSetup, onSearchDone)});
return dialog.exec() == QDialog::Accepted;
}
UpdateInfoPlugin::UpdateInfoPlugin()
{
m_d = new UpdateInfoPluginPrivate;
m_d->m_checkUpdatesTimer = new QTimer(this);
m_d->m_checkUpdatesTimer->setTimerType(Qt::VeryCoarseTimer);
m_d->m_checkUpdatesTimer->setInterval(OneHour);
connect(
m_d->m_checkUpdatesTimer, &QTimer::timeout, this, &UpdateInfoPlugin::doAutoCheckForUpdates);
}
UpdateInfoPlugin::~UpdateInfoPlugin()
{
QDesktopServices::unsetUrlHandler(SERVICE_SCHEME);
stopCheckForUpdates();
if (!m_d->m_maintenanceTool.isEmpty())
saveSettings();
if (m_d->m_service)
ExtensionSystem::PluginManager::removeObject(m_d->m_service.get());
delete m_d;
m_d = nullptr;
}
void UpdateInfoPlugin::startAutoCheckForUpdates()
{
doAutoCheckForUpdates();
m_d->m_checkUpdatesTimer->start();
}
void UpdateInfoPlugin::stopAutoCheckForUpdates()
{
m_d->m_checkUpdatesTimer->stop();
}
void UpdateInfoPlugin::doAutoCheckForUpdates()
{
if (m_d->m_taskTreeRunner.isRunning())
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 (m_d->m_taskTreeRunner.isRunning())
return; // do not trigger while update task is already running
emit checkForUpdatesRunningChanged(true);
const auto onTreeSetup = [](QTaskTree &taskTree) {
m_d->m_progress = new TaskProgress(&taskTree);
using namespace std::chrono_literals;
m_d->m_progress->setHalfLifeTimePerTask(30s);
m_d->m_progress->setDisplayName(Tr::tr("Checking for Updates"));
m_d->m_progress->setKeepOnFinish(FutureProgress::KeepOnFinishTillUserInteraction);
m_d->m_progress->setSubtitleVisibleInStatusBar(true);
};
const auto onTreeDone = [this](DoneWith result) {
if (result == DoneWith::Success)
checkForUpdatesFinished();
checkForUpdatesStopped();
};
const auto doSetup = [](Process &process, const QStringList &args) {
process.setCommand({m_d->m_maintenanceTool, args});
process.setLowPriority();
};
const auto onUpdateSetup = [doSetup](Process &process) {
doSetup(process, {"ch", "-g", "*=false,ifw.package.*=true"});
};
const auto onUpdateDone = [](QString &output) {
return [&output](const Process &process) {
const QString errorMessage = Tr::tr("Failed to get update information (%1): %2")
.arg(process.commandLine().toUserOutput());
if (process.error() != QProcess::UnknownError) {
MessageManager::writeFlashing(errorMessage.arg(process.errorString()));
} else if (process.exitCode() != 0) {
MessageManager::writeFlashing(errorMessage.arg(
Tr::tr("Process finished with exit code %1.").arg(process.exitCode())));
} else {
output = process.cleanedStdOut();
}
};
};
const Group recipe{
ProcessTask(onUpdateSetup, onUpdateDone(m_d->m_updateOutput), CallDone::Always),
m_d->m_settings.checkForQtVersions
? ProcessTask(
[doSetup](Process &process) {
doSetup(
process,
{"se", "qt[.]qt[0-9][.][0-9]+$", "-g", "*=false,ifw.package.*=true"});
},
onUpdateDone(m_d->m_packagesOutput),
CallDone::Always)
: nullItem};
m_d->m_taskTreeRunner.start(recipe, onTreeSetup, onTreeDone);
}
void UpdateInfoPlugin::stopCheckForUpdates()
{
if (!m_d->m_taskTreeRunner.isRunning())
return;
m_d->m_taskTreeRunner.reset();
checkForUpdatesStopped();
}
void UpdateInfoPlugin::checkForUpdatesStopped()
{
m_d->m_updateOutput.clear();
m_d->m_packagesOutput.clear();
emit checkForUpdatesRunningChanged(false);
}
static QString infoTitle(const QList &updates, const std::optional &newQt)
{
static QString blogUrl("href=\"https://www.qt.io/blog/tag/releases\"");
if (!updates.isEmpty() && newQt) {
return Tr::tr(
"%1 and other updates are available. Check the Qt blog for details.")
.arg(newQt->displayName, blogUrl);
} else if (newQt) {
return Tr::tr("%1 is available. Check the Qt blog for details.")
.arg(newQt->displayName, blogUrl);
}
return Tr::tr("New updates are available. Start the update?");
}
static void showUpdateInfo(const QList &updates,
const std::optional &newQt,
const std::function &startUpdater,
const std::function &startPackageManager)
{
InfoBarEntry info(InstallUpdates, infoTitle(updates, newQt));
info.setTitle(Tr::tr("Updates Available"));
info.setInfoType(InfoLabel::Information);
info.addCustomButton(
Tr::tr("Open Settings"),
[] { ICore::showOptionsDialog(FILTER_OPTIONS_PAGE_ID); },
{},
InfoBarEntry::ButtonAction::Hide);
if (newQt) {
info.addCustomButton(
Tr::tr("Start Package Manager"),
[startPackageManager] { startPackageManager(); },
{},
InfoBarEntry::ButtonAction::Hide);
} else {
info.addCustomButton(
Tr::tr("Start Update"),
[startUpdater] { startUpdater(); },
{},
InfoBarEntry::ButtonAction::Hide);
}
if (!updates.isEmpty()) {
info.setDetailsWidgetCreator([updates, newQt] {
const QString qtText = newQt ? (newQt->displayName + "") : QString();
const QStringList packageNames = Utils::transform(updates, [](const Update &u) {
if (u.version.isEmpty())
return u.name;
return Tr::tr("%1 (%2)", "Package name and version").arg(u.name, u.version);
});
const QString updateText = packageNames.join("");
auto label = new QLabel;
label->setText("" + Tr::tr("Available updates:") + "
- "
+ qtText + updateText + "
");
label->setContentsMargins(2, 2, 2, 2);
auto scrollArea = new QScrollArea;
scrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
scrollArea->setWidget(label);
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->viewport()->setAutoFillBackground(false);
label->setAutoFillBackground(false);
//: in the sense "details of the update"
scrollArea->setWindowTitle(Tr::tr("Update Details"));
return scrollArea;
});
}
InfoBar *infoBar = ICore::popupInfoBar();
infoBar->removeInfo(InstallUpdates); // remove any existing notifications
infoBar->unsuppressInfo(InstallUpdates);
infoBar->addInfo(info);
}
void UpdateInfoPlugin::checkForUpdatesFinished()
{
setLastCheckDate(QDate::currentDate());
qCDebug(updateLog) << "--- MaintenanceTool output (updates):";
qCDebug(updateLog) << qPrintable(m_d->m_updateOutput);
qCDebug(updateLog) << "--- MaintenanceTool output (packages):";
qCDebug(updateLog) << qPrintable(m_d->m_packagesOutput);
const QList updates = availableUpdates(m_d->m_updateOutput);
const QList qtPackages = availableQtPackages(m_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 qtToNag = qtToNagAbout(qtPackages, &m_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 (m_d->m_progress)
m_d->m_progress->setKeepOnFinish(FutureProgress::HideOnFinish);
emit newUpdatesAvailable(true);
showUpdateInfo(
updates, qtToNag, [this] { startUpdater(); }, [this] { startPackageManager(); });
} else {
if (m_d->m_progress)
m_d->m_progress->setSubtitle(Tr::tr("No updates found."));
emit newUpdatesAvailable(false);
}
}
void UpdateInfoPlugin::installPackagesHandler(const QUrl &url)
{
QTC_ASSERT(url.scheme() == SERVICE_SCHEME, return);
QTC_ASSERT(url.toString().startsWith(SERVICE_URL), return);
if (!url.hasQuery())
return;
const QString query = url.query();
const QString regex = "(" + query.split(';').join(")|(") + ")";
m_d->m_service->installPackages(regex);
}
bool UpdateInfoPlugin::isCheckForUpdatesRunning() const
{
return m_d->m_taskTreeRunner.isRunning();
}
void UpdateInfoPlugin::extensionsInitialized()
{
if (isAutomaticCheck())
QTimer::singleShot(OneMinute, this, &UpdateInfoPlugin::startAutoCheckForUpdates);
}
Result<> UpdateInfoPlugin::initialize(const QStringList &)
{
loadSettings();
if (m_d->m_maintenanceTool.isEmpty()) {
return ResultError(Tr::tr("Could not determine location of maintenance tool. Please check "
"your installation if you did not enable this plugin manually."));
}
if (!m_d->m_maintenanceTool.isExecutableFile()) {
m_d->m_maintenanceTool.clear();
return ResultError(
Tr::tr("The maintenance tool at \"%1\" is not an executable. Check your installation.")
.arg(m_d->m_maintenanceTool.toUserOutput()));
}
connect(ICore::instance(), &ICore::saveSettingsRequested,
this, &UpdateInfoPlugin::saveSettings);
setupSettings(this);
auto mtools = ActionManager::actionContainer(Constants::M_TOOLS);
ActionContainer *mmaintenanceTool = ActionManager::createMenu(M_MAINTENANCE_TOOL);
mmaintenanceTool->setOnAllDisabledBehavior(ActionContainer::Hide);
mmaintenanceTool->menu()->setTitle(Tr::tr("Qt Maintenance Tool"));
mtools->addMenu(mmaintenanceTool);
QAction *checkForUpdatesAction = new QAction(Tr::tr("Check for Updates"), this);
checkForUpdatesAction->setMenuRole(QAction::ApplicationSpecificRole);
Command *checkForUpdatesCommand = 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);
Command *startMaintenanceToolCommand = ActionManager::registerAction(startMaintenanceToolAction,
"Updates.StartMaintenanceTool");
connect(startMaintenanceToolAction, &QAction::triggered, this, [this] {
startMaintenanceTool({});
});
mmaintenanceTool->addAction(startMaintenanceToolCommand);
m_d->m_service.reset(new ServiceImpl);
ExtensionSystem::PluginManager::addObject(m_d->m_service.get());
QDesktopServices::setUrlHandler(SERVICE_SCHEME, this, "installPackagesHandler");
return ResultOk;
}
void UpdateInfoPlugin::loadSettings() const
{
UpdateInfoPluginPrivate::Settings def;
QtcSettings *settings = ICore::settings();
const Key updaterKey = Key(UpdaterGroup) + '/';
m_d->m_maintenanceTool = FilePath::fromSettings(
settings->value(updaterKey + MaintenanceToolKey));
m_d->m_lastCheckDate = settings->value(updaterKey + LastCheckDateKey, QDate()).toDate();
m_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)
m_d->m_settings.checkInterval = static_cast(newValue);
}
const QString lastMaxQtVersionString = settings->value(updaterKey + LastMaxQtVersionKey)
.toString();
m_d->m_lastMaxQtVersion = QVersionNumber::fromString(lastMaxQtVersionString);
m_d->m_settings.checkForQtVersions
= settings->value(updaterKey + CheckForNewQtVersionsKey, def.checkForQtVersions).toBool();
}
void UpdateInfoPlugin::saveSettings()
{
UpdateInfoPluginPrivate::Settings def;
QtcSettings *settings = ICore::settings();
settings->beginGroup(UpdaterGroup);
settings->setValueWithDefault(LastCheckDateKey, m_d->m_lastCheckDate, QDate());
settings
->setValueWithDefault(AutomaticCheckKey, m_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(m_d->m_settings.checkInterval)),
QString::fromUtf8(me.valueToKey(def.checkInterval)));
settings->setValueWithDefault(LastMaxQtVersionKey, m_d->m_lastMaxQtVersion.toString());
settings->setValueWithDefault(
CheckForNewQtVersionsKey, m_d->m_settings.checkForQtVersions, def.checkForQtVersions);
settings->endGroup();
}
bool UpdateInfoPlugin::isAutomaticCheck() const
{
return m_d->m_settings.automaticCheck;
}
void UpdateInfoPlugin::setAutomaticCheck(bool on)
{
if (m_d->m_settings.automaticCheck == on)
return;
m_d->m_settings.automaticCheck = on;
if (on)
startAutoCheckForUpdates();
else
stopAutoCheckForUpdates();
}
UpdateInfoPlugin::CheckUpdateInterval UpdateInfoPlugin::checkUpdateInterval() const
{
return m_d->m_settings.checkInterval;
}
void UpdateInfoPlugin::setCheckUpdateInterval(UpdateInfoPlugin::CheckUpdateInterval interval)
{
if (m_d->m_settings.checkInterval == interval)
return;
m_d->m_settings.checkInterval = interval;
}
bool UpdateInfoPlugin::isCheckingForQtVersions() const
{
return m_d->m_settings.checkForQtVersions;
}
void UpdateInfoPlugin::setCheckingForQtVersions(bool on)
{
m_d->m_settings.checkForQtVersions = on;
}
QDate UpdateInfoPlugin::lastCheckDate() const
{
return m_d->m_lastCheckDate;
}
void UpdateInfoPlugin::setLastCheckDate(const QDate &date)
{
if (m_d->m_lastCheckDate == date)
return;
m_d->m_lastCheckDate = date;
emit lastCheckDateChanged(date);
}
QDate UpdateInfoPlugin::nextCheckDate() const
{
return nextCheckDate(m_d->m_settings.checkInterval);
}
QDate UpdateInfoPlugin::nextCheckDate(CheckUpdateInterval interval) const
{
if (!m_d->m_lastCheckDate.isValid())
return QDate();
if (interval == DailyCheck)
return m_d->m_lastCheckDate.addDays(1);
if (interval == WeeklyCheck)
return m_d->m_lastCheckDate.addDays(7);
return m_d->m_lastCheckDate.addMonths(1);
}
void UpdateInfoPlugin::startMaintenanceTool(const QStringList &args) const
{
Process::startDetached(CommandLine{m_d->m_maintenanceTool, args});
}
void UpdateInfoPlugin::startUpdater() const
{
startMaintenanceTool({"--updater"});
}
void UpdateInfoPlugin::startPackageManager() const
{
startMaintenanceTool({"--start-package-manager"});
}
} //namespace Internal
} //namespace UpdateInfo
#include "updateinfoplugin.moc"