diff options
Diffstat (limited to 'src/libs/installer')
29 files changed, 368 insertions, 400 deletions
diff --git a/src/libs/installer/adminauthorization_x11.cpp b/src/libs/installer/adminauthorization_x11.cpp index 14691c9de..f4951d523 100644 --- a/src/libs/installer/adminauthorization_x11.cpp +++ b/src/libs/installer/adminauthorization_x11.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -28,41 +28,18 @@ #include "adminauthorization.h" -#include <QtCore/QFile> -#include <QtCore/QRegExp> -#include <QDebug> +#include "globals.h" +#include <QDebug> #include <QApplication> -#include <QInputDialog> #include <QMessageBox> +#include <QProcess> -#include <cstdlib> -#include <sys/resource.h> #include <unistd.h> -#include <fcntl.h> - -#ifdef Q_OS_LINUX -#include <linux/limits.h> -#include <pty.h> -#else -#ifdef Q_OS_FREEBSD -#include <libutil.h> -#include <signal.h> -#else -#include <util.h> -#endif -#endif -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <errno.h> #include <iostream> -#include "globals.h" - -#define SU_COMMAND "/usr/bin/sudo" -//#define SU_COMMAND "/bin/echo" +#define PKEXEC_COMMAND "/usr/bin/pkexec" namespace QInstaller { @@ -72,24 +49,6 @@ namespace QInstaller { \internal */ -static QString getPassword(QWidget *parent) -{ - if (qobject_cast<QApplication*> (qApp) != 0) { - bool ok = false; - const QString result = QInputDialog::getText(parent, QObject::tr("Authorization required"), - QObject::tr("Enter your password to authorize for sudo:"), - QLineEdit::Password, QString(), &ok); - return ok ? result : QString(); - } else { - std::cout << QObject::tr("Authorization required").toStdString() << std::endl; - std::cout << QObject::tr("Enter your password to authorize for sudo:").toStdString() - << std::endl; - std::string password; - std::cin >> password; - return QString::fromStdString(password); - } -} - static void printError(QWidget *parent, const QString &value) { if (qobject_cast<QApplication*> (qApp) != 0) { @@ -105,184 +64,21 @@ bool AdminAuthorization::execute(QWidget *parent, const QString &program, const const QString fallback = program + QLatin1String(" ") + arguments.join(QLatin1String(" ")); qCDebug(QInstaller::lcServer) << "Fallback:" << fallback; - // as we cannot pipe the password to su in QProcess, we need to setup a pseudo-terminal for it - int masterFD = -1; - int slaveFD = -1; - char ptsn[ PATH_MAX ]; + QProcess process; + process.setProcessChannelMode(QProcess::MergedChannels); + process.start(QLatin1String(PKEXEC_COMMAND), QStringList() << program << arguments, QIODevice::ReadOnly); - if (::openpty(&masterFD, &slaveFD, ptsn, 0, 0)) - return false; - - masterFD = ::posix_openpt(O_RDWR | O_NOCTTY); - if (masterFD < 0) - return false; - - const QByteArray ttyName = ::ptsname(masterFD); - - if (::grantpt(masterFD)) { - ::close(masterFD); - return false; - } - - ::revoke(ttyName); - ::unlockpt(masterFD); - - slaveFD = ::open(ttyName, O_RDWR | O_NOCTTY); - if (slaveFD < 0) { - ::close(masterFD); + if (!process.waitForStarted() || !process.waitForFinished(-1)) { + printError(parent, process.errorString()); + if (process.state() > QProcess::NotRunning) + process.kill(); return false; } - - ::fcntl(masterFD, F_SETFD, FD_CLOEXEC); - ::fcntl(slaveFD, F_SETFD, FD_CLOEXEC); - int pipedData[2]; - if (pipe(pipedData) != 0) - return false; - - int flags = ::fcntl(pipedData[0], F_GETFL); - if (flags != -1) - ::fcntl(pipedData[0], F_SETFL, flags | O_NONBLOCK); - - flags = ::fcntl(masterFD, F_GETFL); - if (flags != -1) - ::fcntl(masterFD, F_SETFL, flags | O_NONBLOCK); - - pid_t child = fork(); - - if (child < -1) { - ::close(masterFD); - ::close(slaveFD); - ::close(pipedData[0]); - ::close(pipedData[1]); - return false; - } - - // parent process - else if (child > 0) { - ::close(slaveFD); - //close writing end of pipe - ::close(pipedData[1]); - - QRegExp re(QLatin1String("[Pp]assword.*:")); - QByteArray data; - QByteArray errData; - int bytes = 0; - int errBytes = 0; - char buf[1024]; - char errBuf[1024]; - int status; - bool statusValid = false; - while (bytes >= 0) { - const pid_t waitResult = ::waitpid(child, &status, WNOHANG); - if (waitResult == -1) { - break; - } - if (waitResult == child) { - statusValid = true; - break; - } - bytes = ::read(masterFD, buf, 1023); - if (bytes == -1 && errno == EAGAIN) - bytes = 0; - else if (bytes > 0) - data.append(buf, bytes); - errBytes = ::read(pipedData[0], errBuf, 1023); - if (errBytes > 0) - { - errData.append(errBuf, errBytes); - errBytes=0; - } - if (bytes > 0) { - const QString line = QString::fromLatin1(data); - if (re.indexIn(line) != -1) { - const QString password = getPassword(parent); - if (password.isEmpty()) { - QByteArray pwd = password.toLatin1(); - for (int i = 0; i < 3; ++i) { - ::write(masterFD, pwd.data(), pwd.length()); - ::write(masterFD, "\n", 1); - } - return false; - } - QByteArray pwd = password.toLatin1(); - ::write(masterFD, pwd.data(), pwd.length()); - ::write(masterFD, "\n", 1); - ::read(masterFD, buf, pwd.length() + 1); - } - } - if (bytes == 0) - ::usleep(100000); - } - - while (true) { - errBytes = ::read(pipedData[0], errBuf, 1023); - if (errBytes == -1 && errno == EAGAIN) { - ::usleep(100000); - continue; - } - - if (errBytes <= 0) - break; - - errData.append(errBuf, errBytes); - } - - const bool success = statusValid && WIFEXITED(status) && WEXITSTATUS(status) == 0; - - if (!success && !errData.isEmpty()) { - printError(parent, QString::fromLocal8Bit(errData.constData())); - } - - ::close(pipedData[0]); - return success; - } - - // child process - else { - ::close(pipedData[0]); - // Reset signal handlers - for (int sig = 1; sig < NSIG; ++sig) - signal(sig, SIG_DFL); - signal(SIGHUP, SIG_IGN); - - ::setsid(); - - ::ioctl(slaveFD, TIOCSCTTY, 1); - int pgrp = ::getpid(); - ::tcsetpgrp(slaveFD, pgrp); - - ::dup2(slaveFD, 0); - ::dup2(slaveFD, 1); - ::dup2(pipedData[1], 2); - - // close all file descriptors - struct rlimit rlp; - getrlimit(RLIMIT_NOFILE, &rlp); - for (int i = 3; i < static_cast<int>(rlp.rlim_cur); ++i) - ::close(i); - - char **argp = (char **) ::malloc((arguments.count() + 4) * sizeof(char *)); - QList<QByteArray> args; - args.push_back(SU_COMMAND); - args.push_back("-b"); - args.push_back(program.toLocal8Bit()); - for (QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it) - args.push_back(it->toLocal8Bit()); - - int i = 0; - for (QList<QByteArray>::iterator it = args.begin(); it != args.end(); ++it, ++i) - argp[i] = it->data(); - argp[i] = 0; - - ::unsetenv("LANG"); - ::unsetenv("LC_ALL"); - - int exitStatus = 0; - if (::execv(SU_COMMAND, argp) == -1) - exitStatus = -errno; - _exit(exitStatus); + if (process.exitCode() != EXIT_SUCCESS) { + printError(parent, QLatin1String(process.readAll())); return false; } + return true; } // has no guarantee to work diff --git a/src/libs/installer/binarycontent.h b/src/libs/installer/binarycontent.h index a21cd69e7..7d50c0e28 100644 --- a/src/libs/installer/binarycontent.h +++ b/src/libs/installer/binarycontent.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -48,6 +48,13 @@ public: static const qint64 MagicUpdaterMarker = 0x12023235UL; static const qint64 MagicPackageManagerMarker = 0x12023236UL; + // additional distinguishers only used at runtime, not written to the binary itself + enum MagicMarkerSupplement { + Default = 0x0, + OfflineGenerator = 0x1, + PackageViewer = 0x2 + }; + // the cookie put at the end of the file static const quint64 MagicCookie = 0xc2630a1c99d668f8LL; // binary static const quint64 MagicCookieDat = 0xc2630a1c99d668f9LL; // data diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp index 59d42eefc..4bfc4ad3a 100644 --- a/src/libs/installer/commandlineparser.cpp +++ b/src/libs/installer/commandlineparser.cpp @@ -59,6 +59,9 @@ CommandLineParser::CommandLineParser() .arg(CommandLineOptions::scListShort, CommandLineOptions::scListLong) + indent + QString::fromLatin1("%1, %2 - search available packages - <regexp>\n") .arg(CommandLineOptions::scSearchShort, CommandLineOptions::scSearchLong) + + indent + indent + QString::fromLatin1("Note: The --%1 option can be used to specify\n") + .arg(CommandLineOptions::scFilterPackagesLong) + + indent + indent + QLatin1String("additional filters for the search operation\n") + indent + QString::fromLatin1("%1, %2 - create offline installer from selected packages - <pkg ...>\n") .arg(CommandLineOptions::scCreateOfflineShort, CommandLineOptions::scCreateOfflineLong) + indent + QString::fromLatin1("%1, %2 - uninstall all packages and remove entire program directory") @@ -161,6 +164,12 @@ CommandLineParser::CommandLineParser() << CommandLineOptions::scCreateLocalRepositoryShort << CommandLineOptions::scCreateLocalRepositoryLong, QLatin1String("Create a local repository inside the installation directory. This option " "has no effect on online installers."))); + addOptionWithContext(QCommandLineOption(QStringList() + << CommandLineOptions::scFilterPackagesShort << CommandLineOptions::scFilterPackagesLong, + QLatin1String("[CLI] Comma separated list of additional key-value pair filters used to query packages with the " + "search command. The keys can be any of the possible package information elements, like " + "\"DisplayName\" and \"Description\"."), + QLatin1String("element=regex,...")), CommandLineOnly); // Message query options addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptMessageQueryShort diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp index 3b8894b03..6e3856502 100644 --- a/src/libs/installer/component.cpp +++ b/src/libs/installer/component.cpp @@ -301,11 +301,14 @@ void Component::loadDataFromPackage(const KDUpdater::LocalPackage &package) setValue(scCurrentState, scInstalled); setValue(scCheckable, package.checkable ? scTrue : scFalse); setValue(scExpandedByDefault, package.expandedByDefault ? scTrue : scFalse); + setValue(scContentSha1, package.contentSha1); } /*! Sets variables according to the values set in the package.xml file of \a package. Also loads UI files, licenses and translations if they are referenced in the package.xml. + If the \c PackageManagerCore object of this component is run as package viewer, then + only sets the variables without loading referenced files. */ void Component::loadDataFromPackage(const Package &package) { @@ -342,6 +345,10 @@ void Component::loadDataFromPackage(const Package &package) if (PackageManagerCore::noForceInstallation()) forced = scFalse; setValue(scForcedInstallation, forced); + setValue(scContentSha1, package.data(scContentSha1).toString()); + + if (d->m_core->isPackageViewer()) + return; setLocalTempPath(QInstaller::pathFromUrl(package.packageSource().url)); const QStringList uis = package.data(QLatin1String("UserInterfaces")).toString() @@ -369,16 +376,20 @@ quint64 Component::updateUncompressedSize() { quint64 size = 0; - if (installAction() == ComponentModelHelper::Install - || installAction() == ComponentModelHelper::KeepInstalled) { + const bool installOrKeepInstalled = (installAction() == ComponentModelHelper::Install + || installAction() == ComponentModelHelper::KeepInstalled); + + if (installOrKeepInstalled) size = d->m_vars.value(scUncompressedSize).toLongLong(); - } foreach (Component* comp, d->m_allChildComponents) size += comp->updateUncompressedSize(); setValue(scUncompressedSizeSum, QString::number(size)); - setData(humanReadableSize(size), UncompressedSize); + if (size == 0 && !installOrKeepInstalled) + setData(QVariant(), UncompressedSize); + else + setData(humanReadableSize(size), UncompressedSize); return size; } @@ -1643,7 +1654,7 @@ void Component::updateModelData(const QString &key, const QString &data) } if (isUnstable()) { tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. " - "This component can not be installed."); + "This component cannot be installed."); } // replace {external-link}='' fields in component description with proper link tags tooltipText.replace(QRegularExpression(QLatin1String("{external-link}='(.*?)'")), diff --git a/src/libs/installer/component_p.cpp b/src/libs/installer/component_p.cpp index 8533d8e4c..0f74e423c 100644 --- a/src/libs/installer/component_p.cpp +++ b/src/libs/installer/component_p.cpp @@ -54,6 +54,7 @@ ComponentPrivate::ComponentPrivate(PackageManagerCore *core, Component *qq) , m_autoCreateOperations(true) , m_operationsCreatedSuccessfully(true) , m_updateIsAvailable(false) + , m_unstable(false) { } diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp index aab487b45..642828ad7 100644 --- a/src/libs/installer/componentmodel.cpp +++ b/src/libs/installer/componentmodel.cpp @@ -260,9 +260,9 @@ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, in newValue = (oldValue == Qt::Checked) ? Qt::Unchecked : Qt::Checked; } QSet<QModelIndex> changed = updateCheckedState(nodes << component, newValue); - foreach (const QModelIndex &index, changed) { - emit dataChanged(index, index); - emit checkStateChanged(index); + foreach (const QModelIndex &changedIndex, changed) { + emit dataChanged(changedIndex, changedIndex); + emit checkStateChanged(changedIndex); } updateAndEmitModelState(); // update the internal state } else { diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp index 37dc0f13e..a180888d1 100644 --- a/src/libs/installer/componentselectionpage_p.cpp +++ b/src/libs/installer/componentselectionpage_p.cpp @@ -49,6 +49,7 @@ #include <QFileDialog> #include <QStackedLayout> #include <QStackedWidget> +#include <QToolBox> namespace QInstaller { @@ -66,19 +67,29 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP , m_updaterModel(m_core->updaterComponentModel()) , m_currentModel(m_allModel) , m_allowCompressedRepositoryInstall(false) + , m_toolBox(nullptr) + , m_descriptionBaseWidget(nullptr) , m_categoryWidget(Q_NULLPTR) + , m_categoryLayoutVisible(false) { m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); - QVBoxLayout *descriptionVLayout = new QVBoxLayout; + m_descriptionBaseWidget = new QWidget(q); + m_descriptionBaseWidget->setObjectName(QLatin1String("DescriptionBaseWidget")); + + QVBoxLayout *descriptionVLayout = new QVBoxLayout(m_descriptionBaseWidget); descriptionVLayout->setObjectName(QLatin1String("DescriptionLayout")); + descriptionVLayout->setContentsMargins(0, 0, 0, 0); + + m_toolBox = new QToolBox(q); + m_toolBox->setObjectName(QLatin1String("ToolBox")); QScrollArea *descriptionScrollArea = new QScrollArea(q); descriptionScrollArea->setWidgetResizable(true); descriptionScrollArea->setFrameShape(QFrame::NoFrame); descriptionScrollArea->setObjectName(QLatin1String("DescriptionScrollArea")); - m_descriptionLabel = new QLabel(q); + m_descriptionLabel = new QLabel(m_descriptionBaseWidget); m_descriptionLabel->setWordWrap(true); m_descriptionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); m_descriptionLabel->setOpenExternalLinks(true); @@ -87,8 +98,7 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP descriptionScrollArea->setWidget(m_descriptionLabel); descriptionVLayout->addWidget(descriptionScrollArea); - m_sizeLabel = new QLabel(q); - m_sizeLabel->setMargin(5); + m_sizeLabel = new QLabel(m_descriptionBaseWidget); m_sizeLabel->setWordWrap(true); m_sizeLabel->setObjectName(QLatin1String("ComponentSizeLabel")); descriptionVLayout->addWidget(m_sizeLabel); @@ -100,14 +110,14 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP if (m_core->isInstaller()) { m_checkDefault->setObjectName(QLatin1String("SelectDefaultComponentsButton")); m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+A", - "select default components"))); + "Select default components"))); m_checkDefault->setText(ComponentSelectionPage::tr("Def&ault")); m_checkDefault->setToolTip(ComponentSelectionPage::tr("Select default components in the tree view.")); } else { m_checkDefault->setEnabled(false); m_checkDefault->setObjectName(QLatin1String("ResetComponentsButton")); m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+R", - "reset to already installed components"))); + "Reset to already installed components"))); m_checkDefault->setText(ComponentSelectionPage::tr("&Reset")); m_checkDefault->setToolTip( ComponentSelectionPage::tr("Reset all components to their original selection state in the tree view.")); @@ -119,7 +129,7 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP this, &ComponentSelectionPagePrivate::selectAll); m_checkAll->setObjectName(QLatin1String("SelectAllComponentsButton")); m_checkAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+S", - "select all components"))); + "Select all components"))); m_checkAll->setText(ComponentSelectionPage::tr("&Select All")); m_checkAll->setToolTip(ComponentSelectionPage::tr("Select all components in the tree view.")); buttonHLayout->addWidget(m_checkAll); @@ -129,7 +139,7 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP this, &ComponentSelectionPagePrivate::deselectAll); m_uncheckAll->setObjectName(QLatin1String("DeselectAllComponentsButton")); m_uncheckAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+D", - "deselect all components"))); + "Deselect all components"))); m_uncheckAll->setText(ComponentSelectionPage::tr("&Deselect All")); m_uncheckAll->setToolTip(ComponentSelectionPage::tr("Deselect all components in the tree view.")); buttonHLayout->addWidget(m_uncheckAll); @@ -151,11 +161,11 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP QWidget *mainStackedWidget = new QWidget(); m_mainGLayout = new QGridLayout(mainStackedWidget); - m_mainGLayout->addLayout(buttonHLayout, 0, 1); - m_mainGLayout->addLayout(treeViewVLayout, 1, 1); - m_mainGLayout->addLayout(descriptionVLayout, 1, 2); - m_mainGLayout->setColumnStretch(1, 3); - m_mainGLayout->setColumnStretch(2, 2); + m_mainGLayout->addLayout(buttonHLayout, 0, 0); + m_mainGLayout->addLayout(treeViewVLayout, 1, 0); + m_mainGLayout->addWidget(m_descriptionBaseWidget, 1, 1); + m_mainGLayout->setColumnStretch(0, 3); + m_mainGLayout->setColumnStretch(1, 2); m_stackedLayout = new QStackedLayout(q); m_stackedLayout->addWidget(mainStackedWidget); @@ -227,13 +237,13 @@ void ComponentSelectionPagePrivate::setupCategoryLayout() vLayout->setContentsMargins(0, 0, 0, 0); m_categoryWidget->setLayout(vLayout); m_categoryGroupBox = new QGroupBox(q); - m_categoryGroupBox->setTitle(m_core->settings().repositoryCategoryDisplayName()); m_categoryGroupBox->setObjectName(QLatin1String("CategoryGroupBox")); QVBoxLayout *categoryLayout = new QVBoxLayout(m_categoryGroupBox); QPushButton *fetchCategoryButton = new QPushButton(tr("Filter")); fetchCategoryButton->setObjectName(QLatin1String("FetchCategoryButton")); + fetchCategoryButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); fetchCategoryButton->setToolTip( - ComponentSelectionPage::tr("Filter the enabled repository categories to selection.")); + ComponentSelectionPage::tr("Filter the enabled repository categories")); connect(fetchCategoryButton, &QPushButton::clicked, this, &ComponentSelectionPagePrivate::fetchRepositoryCategories); @@ -249,16 +259,29 @@ void ComponentSelectionPagePrivate::setupCategoryLayout() vLayout->addWidget(m_categoryGroupBox); vLayout->addStretch(); - m_mainGLayout->addWidget(m_categoryWidget, 1, 0); + m_toolBox->insertItem(1, m_categoryWidget, m_core->settings().repositoryCategoryDisplayName()); } void ComponentSelectionPagePrivate::showCategoryLayout(bool show) { + if (!show && !m_categoryWidget) + return; + + if (show == m_categoryLayoutVisible) + return; + + setupCategoryLayout(); if (show) { - setupCategoryLayout(); + m_mainGLayout->removeWidget(m_descriptionBaseWidget); + m_toolBox->insertItem(0, m_descriptionBaseWidget, tr("Component Information")); + m_mainGLayout->addWidget(m_toolBox, 1, 1); + } else { + m_toolBox->removeItem(0); + m_mainGLayout->removeWidget(m_toolBox); + m_mainGLayout->addWidget(m_descriptionBaseWidget, 1, 1); } - if (m_categoryWidget) - m_categoryWidget->setVisible(show); + m_toolBox->setVisible(show); + m_categoryLayoutVisible = show; } void ComponentSelectionPagePrivate::updateTreeView() @@ -395,10 +418,9 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories() { updateWidgetVisibility(true); - QCheckBox *checkbox; QList<QCheckBox*> checkboxes = m_categoryGroupBox->findChildren<QCheckBox *>(); for (int i = 0; i < checkboxes.count(); i++) { - checkbox = checkboxes.at(i); + QCheckBox *checkbox = checkboxes.at(i); enableRepositoryCategory(checkbox->objectName(), checkbox->isChecked()); } diff --git a/src/libs/installer/componentselectionpage_p.h b/src/libs/installer/componentselectionpage_p.h index 37f808286..d2d30e3f0 100644 --- a/src/libs/installer/componentselectionpage_p.h +++ b/src/libs/installer/componentselectionpage_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -46,6 +46,7 @@ class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QStackedLayout; +class QToolBox; namespace QInstaller { @@ -88,6 +89,8 @@ private: ComponentSelectionPage *q; PackageManagerCore *m_core; QTreeView *m_treeView; + QToolBox *m_toolBox; + QWidget *m_descriptionBaseWidget; QLabel *m_sizeLabel; QLabel *m_descriptionLabel; QPushButton *m_checkAll; @@ -99,6 +102,7 @@ private: QProgressBar *m_progressBar; QGridLayout *m_mainGLayout; bool m_allowCompressedRepositoryInstall; + bool m_categoryLayoutVisible; ComponentModel *m_allModel; ComponentModel *m_updaterModel; ComponentModel *m_currentModel; diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h index 42b14ce63..0e16d4c4b 100644 --- a/src/libs/installer/constants.h +++ b/src/libs/installer/constants.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -67,6 +67,7 @@ static const QLatin1String scUncompressedSizeSum("UncompressedSizeSum"); static const QLatin1String scRequiresAdminRights("RequiresAdminRights"); static const QLatin1String scOfflineBinaryName("OfflineBinaryName"); static const QLatin1String scSHA1("SHA1"); +static const QLatin1String scContentSha1("ContentSha1"); // constants used throughout the components class static const QLatin1String scVirtual("Virtual"); @@ -202,6 +203,8 @@ static const QLatin1String scCreateLocalRepositoryShort("cl"); static const QLatin1String scCreateLocalRepositoryLong("create-local-repository"); static const QLatin1String scNoDefaultInstallationShort("nd"); static const QLatin1String scNoDefaultInstallationLong("no-default-installations"); +static const QLatin1String scFilterPackagesShort("fp"); +static const QLatin1String scFilterPackagesLong("filter-packages"); // Developer options static const QLatin1String scScriptShort("s"); diff --git a/src/libs/installer/downloadarchivesjob.cpp b/src/libs/installer/downloadarchivesjob.cpp index 5dbccc0b6..c7ba9dc80 100644 --- a/src/libs/installer/downloadarchivesjob.cpp +++ b/src/libs/installer/downloadarchivesjob.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -32,6 +32,7 @@ #include "messageboxhandler.h" #include "packagemanagercore.h" #include "utils.h" +#include "fileutils.h" #include "filedownloader.h" #include "filedownloaderfactory.h" @@ -55,6 +56,8 @@ DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core) , m_canceled(false) , m_lastFileProgress(0) , m_progressChangedTimerId(0) + , m_totalSizeToDownload(0) + , m_totalSizeDownloaded(0) { setCapabilities(Cancelable); } @@ -79,10 +82,19 @@ void DownloadArchivesJob::setArchivesToDownload(const QList<QPair<QString, QStri } /*! + Sets the expected total size of archives to download to \a total. +*/ +void DownloadArchivesJob::setExpectedTotalSize(quint64 total) +{ + m_totalSizeToDownload = total; +} + +/*! \reimp */ void DownloadArchivesJob::doStart() { + m_totalDownloadSpeedTimer.start(); m_archivesDownloaded = 0; fetchNextArchiveHash(); } @@ -198,6 +210,70 @@ void DownloadArchivesJob::timerEvent(QTimerEvent *event) } /*! + Builds a textual representation of the total download \a status and + emits the \c {downloadStatusChanged()} signal. +*/ +void DownloadArchivesJob::onDownloadStatusChanged(const QString &status) +{ + if (!m_downloader || m_canceled) { + emit downloadStatusChanged(status); + return; + } + + QString extendedStatus; + quint64 currentDownloaded = m_totalSizeDownloaded + m_downloader->getBytesReceived(); + if (m_totalSizeToDownload > 0) { + QString bytesReceived = humanReadableSize(currentDownloaded); + const QString bytesToReceive = humanReadableSize(m_totalSizeToDownload); + + // remove the unit from the bytesReceived value if bytesToReceive has the same + const QString tmp = bytesToReceive.mid(bytesToReceive.indexOf(QLatin1Char(' '))); + if (bytesReceived.endsWith(tmp)) + bytesReceived.chop(tmp.length()); + + extendedStatus = tr("%1 of %2").arg(bytesReceived, bytesToReceive); + } else if (currentDownloaded > 0) { + extendedStatus = tr("%1 downloaded.").arg(humanReadableSize(currentDownloaded)); + } + + const quint64 totalDownloadSpeed = currentDownloaded + / double(m_totalDownloadSpeedTimer.elapsed() / 1000); + + if (m_totalSizeToDownload > 0 && totalDownloadSpeed > 0) { + const qint64 time = (m_totalSizeToDownload - currentDownloaded) / totalDownloadSpeed; + + int s = time % 60; + const int d = time / 86400; + const int h = (time / 3600) - (d * 24); + const int m = (time / 60) - (d * 1440) - (h * 60); + + QString days; + if (d > 0) + days = tr("%n day(s), ", "", d); + + QString hours; + if (h > 0) + hours = tr("%n hour(s), ", "", h); + + QString minutes; + if (m > 0) + minutes = tr("%n minute(s)", "", m); + + QString seconds; + if (s >= 0 && minutes.isEmpty()) { + s = (s <= 0 ? 1 : s); + seconds = tr("%n second(s)", "", s); + } + extendedStatus += tr(" - %1%2%3%4 remaining.").arg(days, hours, minutes, seconds); + } else { + extendedStatus += tr(" - unknown time remaining."); + } + + emit downloadStatusChanged(QLatin1String("Archive: ") + status + + QLatin1String("<br>Total: ") + extendedStatus); +} + +/*! Registers the just downloaded file in the installer's file system. */ void DownloadArchivesJob::registerFile() @@ -224,6 +300,7 @@ void DownloadArchivesJob::registerFile() } } else { ++m_archivesDownloaded; + m_totalSizeDownloaded += QFile(m_downloader->downloadedFileName()).size(); if (m_progressChangedTimerId) { killTimer(m_progressChangedTimerId); m_progressChangedTimerId = 0; @@ -295,7 +372,7 @@ KDUpdater::FileDownloader *DownloadArchivesJob::setupDownloader(const QString &s connect(downloader, &FileDownloader::downloadCanceled, this, &DownloadArchivesJob::downloadCanceled); connect(downloader, &FileDownloader::downloadAborted, this, &DownloadArchivesJob::downloadFailed, Qt::QueuedConnection); - connect(downloader, &FileDownloader::downloadStatus, this, &DownloadArchivesJob::downloadStatusChanged); + connect(downloader, &FileDownloader::downloadStatus, this, &DownloadArchivesJob::onDownloadStatusChanged); if (FileDownloaderFactory::isSupportedScheme(scheme)) { downloader->setDownloadedFileName(component->localTempPath() + QLatin1Char('/') diff --git a/src/libs/installer/downloadarchivesjob.h b/src/libs/installer/downloadarchivesjob.h index 8f2392064..bd764e01c 100644 --- a/src/libs/installer/downloadarchivesjob.h +++ b/src/libs/installer/downloadarchivesjob.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -32,6 +32,7 @@ #include "job.h" #include <QtCore/QPair> +#include <QtCore/QElapsedTimer> QT_BEGIN_NAMESPACE class QTimerEvent; @@ -56,6 +57,7 @@ public: int numberOfDownloads() const { return m_archivesDownloaded; } void setArchivesToDownload(const QList<QPair<QString, QString> > &archives); + void setExpectedTotalSize(quint64 total); Q_SIGNALS: void progressChanged(double progress); @@ -67,6 +69,9 @@ protected: void doCancel(); void timerEvent(QTimerEvent *event); +public Q_SLOTS: + void onDownloadStatusChanged(const QString &status); + protected Q_SLOTS: void registerFile(); void downloadCanceled(); @@ -92,6 +97,10 @@ private: QByteArray m_currentHash; double m_lastFileProgress; int m_progressChangedTimerId; + + quint64 m_totalSizeToDownload; + quint64 m_totalSizeDownloaded; + QElapsedTimer m_totalDownloadSpeedTimer; }; } // namespace QInstaller diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp index 4ca9a8e59..fb1778fe0 100644 --- a/src/libs/installer/elevatedexecuteoperation.cpp +++ b/src/libs/installer/elevatedexecuteoperation.cpp @@ -169,7 +169,7 @@ int ElevatedExecuteOperation::Private::run(QStringList &arguments, const Operati << workingDirectory; } - QProcessEnvironment penv; + QProcessEnvironment penv = QProcessEnvironment::systemEnvironment(); // there is no way to serialize a QProcessEnvironment properly other than per mangled QStringList: // (i.e. no other way to list all keys) process->setEnvironment(KDUpdater::Environment::instance().applyTo(penv).toStringList()); diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index bc4fcbb39..c28a840bd 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -230,6 +230,7 @@ unix { } LIBS += -l7z +CONFIG(libarchive): LIBS += -llibarchive win32 { SOURCES += adminauthorization_win.cpp sysinfo_win.cpp diff --git a/src/libs/installer/installiconsoperation.cpp b/src/libs/installer/installiconsoperation.cpp index 9443b7f71..de1ddcc4f 100644 --- a/src/libs/installer/installiconsoperation.cpp +++ b/src/libs/installer/installiconsoperation.cpp @@ -134,8 +134,8 @@ bool InstallIconsOperation::performOperation() if (status == PackageManagerCore::Canceled || status == PackageManagerCore::Failure) return true; - const QString &source = it.next(); - QString target = targetDir.absoluteFilePath(sourceDir.relativeFilePath(source)); + const QString &source2 = it.next(); + QString target = targetDir.absoluteFilePath(sourceDir.relativeFilePath(source2)); emit outputTextChanged(target); @@ -185,7 +185,7 @@ bool InstallIconsOperation::performOperation() } // copy the file to its new location - QFile cf(source); + QFile cf(source2); if (!cf.copy(target)) { setError(UserDefinedError); setErrorString(tr("Failed to copy file \"%1\": %2").arg( @@ -193,8 +193,8 @@ bool InstallIconsOperation::performOperation() undoOperation(); return false; } - deleteFileNowOrLater(source); - files.push_back(source); + deleteFileNowOrLater(source2); + files.push_back(source2); files.push_back(target); setValue(QLatin1String("files"), files); } else if (fi.isDir() && !QDir(target).exists()) { diff --git a/src/libs/installer/keepaliveobject.cpp b/src/libs/installer/keepaliveobject.cpp index 94e91efd8..18ec9743a 100644 --- a/src/libs/installer/keepaliveobject.cpp +++ b/src/libs/installer/keepaliveobject.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -48,7 +48,12 @@ KeepAliveObject::KeepAliveObject() void KeepAliveObject::start() { + if (m_timer) + delete m_timer; m_timer = new QTimer(this); + + if (m_socket) + delete m_socket; m_socket = new QLocalSocket(this); connect(m_timer, &QTimer::timeout, [this]() { diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp index fc6ac7ae4..2f33c95e3 100644 --- a/src/libs/installer/lib7z_facade.cpp +++ b/src/libs/installer/lib7z_facade.cpp @@ -261,7 +261,7 @@ QString errorMessageFrom7zResult(const LONG &extractResult) if (!lastError().isEmpty()) return lastError(); - QString errorMessage = QCoreApplication::translate("Lib7z", "internal code: %1"); + QString errorMessage = QCoreApplication::translate("Lib7z", "Internal code: %1"); switch (extractResult) { case S_OK: qFatal("S_OK value is not a valid error code."); @@ -282,7 +282,7 @@ QString errorMessageFrom7zResult(const LONG &extractResult) errorMessage = errorMessage.arg(QLatin1String("STG_E_INVALIDFUNCTION")); break; case E_OUTOFMEMORY: - errorMessage = QCoreApplication::translate("Lib7z", "not enough memory"); + errorMessage = QCoreApplication::translate("Lib7z", "Not enough memory"); break; case E_INVALIDARG: errorMessage = errorMessage.arg(QLatin1String("E_INVALIDARG")); diff --git a/src/libs/installer/loggingutils.cpp b/src/libs/installer/loggingutils.cpp index 0ebba10db..45f7aab1b 100644 --- a/src/libs/installer/loggingutils.cpp +++ b/src/libs/installer/loggingutils.cpp @@ -259,7 +259,7 @@ void LoggingHandler::printComponentInfo(const QList<Component *> components) con QDomElement root = doc.createElement(QLatin1String("updates")); doc.appendChild(root); - foreach (Component *component, components) { + foreach (const Component *component, components) { QDomElement update = doc.createElement(QLatin1String("update")); update.setAttribute(QLatin1String("name"), component->value(scDisplayName)); update.setAttribute(QLatin1String("version"), component->value(scVersion)); diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp index 7a6866dc2..68a242ebc 100644 --- a/src/libs/installer/metadatajob.cpp +++ b/src/libs/installer/metadatajob.cpp @@ -180,10 +180,6 @@ void MetadataJob::doStart() item.insert(TaskRole::Authenticator, QVariant::fromValue(authenticator)); items.append(item); } - else { - qCWarning(QInstaller::lcInstallerInstallLog) << "Trying to parse compressed repo as " - "normal repository. Check repository syntax."; - } } } if (items.count() > 0) { @@ -238,7 +234,7 @@ void MetadataJob::startXMLTask(const QList<FileTaskItem> &items) void MetadataJob::doCancel() { reset(); - emitFinishedWithError(Job::Canceled, tr("Meta data download canceled.")); + emitFinishedWithError(Job::Canceled, tr("Metadata download canceled.")); } void MetadataJob::startUnzipRepositoryTask(const Repository &repo) diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index 145eb30d7..232024f66 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -764,6 +764,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize) Q_ASSERT(partProgressSize >= 0 && partProgressSize <= 1); QList<QPair<QString, QString> > archivesToDownload; + quint64 archivesToDownloadTotalSize = 0; QList<Component*> neededComponents = orderedComponentsToInstall(); foreach (Component *component, neededComponents) { // collect all archives to be downloaded @@ -773,6 +774,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize) .arg(component->name(), versionFreeString), QString::fromLatin1("%1/%2/%3") .arg(component->repositoryUrl().toString(), component->name(), versionFreeString))); } + archivesToDownloadTotalSize += component->value(scCompressedSize).toULongLong(); } if (archivesToDownload.isEmpty()) @@ -783,6 +785,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize) DownloadArchivesJob archivesJob(this); archivesJob.setAutoDelete(false); archivesJob.setArchivesToDownload(archivesToDownload); + archivesJob.setExpectedTotalSize(archivesToDownloadTotalSize); connect(this, &PackageManagerCore::installationInterrupted, &archivesJob, &Job::cancel); connect(&archivesJob, &DownloadArchivesJob::outputTextChanged, ProgressCoordinator::instance(), &ProgressCoordinator::emitLabelAndDetailTextChanged); @@ -847,7 +850,7 @@ void PackageManagerCore::setNeedsHardRestart(bool needsHardRestart) */ void PackageManagerCore::rollBackInstallation() { - emit titleMessageChanged(tr("Cancelling the Installer")); + emit titleMessageChanged(tr("Canceling the Installer")); // this unregisters all operation progressChanged connected ProgressCoordinator::instance()->setUndoMode(); @@ -1477,20 +1480,16 @@ bool PackageManagerCore::fetchCompressedPackagesTree() if (!isInstaller() && status() == Failure) return false; - if (!d->fetchMetaInformationFromCompressedRepositories()) + if (!d->fetchMetaInformationFromRepositories(DownloadType::CompressedPackage)) return false; if (!d->addUpdateResourcesFromRepositories(true, true)) { return false; } - PackagesList packages; - const PackagesList &compPackages = d->compressedPackages(); - if (compPackages.isEmpty()) + const PackagesList &packages = d->remotePackages(); + if (packages.isEmpty()) return false; - packages.append(compPackages); - const PackagesList &rPackages = d->remotePackages(); - packages.append(rPackages); return fetchPackagesTree(packages, installedPackages); } @@ -1520,7 +1519,7 @@ bool PackageManagerCore::fetchRemotePackagesTree() if (!d->fetchMetaInformationFromRepositories()) return false; - if (!d->fetchMetaInformationFromCompressedRepositories()) + if (!d->fetchMetaInformationFromRepositories(DownloadType::CompressedPackage)) return false; if (!d->addUpdateResourcesFromRepositories(true)) @@ -1556,9 +1555,8 @@ bool PackageManagerCore::fetchPackagesTree(const PackagesList &packages, const L continue; const LocalPackage localPackage = installedPackages.value(name); - const QString updateVersion = update->data(scVersion).toString(); - if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0) - continue; // remote version equals or is less than the installed maintenance tool + if (!d->packageNeedsUpdate(localPackage, update)) + continue; const QDate updateDate = update->data(scReleaseDate).toDate(); if (localPackage.lastUpdateDate >= updateDate) @@ -2192,26 +2190,50 @@ ComponentModel *PackageManagerCore::updaterComponentModel() const /*! Lists available packages filtered with \a regexp without GUI. Virtual - components are not listed unless set visible. + components are not listed unless set visible. Optionally, a \a filters + hash containing package information elements and regular expressions + can be used to further filter listed packages. \sa setVirtualComponentsVisible() */ -void PackageManagerCore::listAvailablePackages(const QString ®exp) +void PackageManagerCore::listAvailablePackages(const QString ®exp, const QHash<QString, QString> &filters) { + setPackageViewer(); qCDebug(QInstaller::lcInstallerInstallLog) << "Searching packages with regular expression:" << regexp; + + ComponentModel *model = defaultComponentModel(); d->fetchMetaInformationFromRepositories(DownloadType::UpdatesXML); d->addUpdateResourcesFromRepositories(true); QRegularExpression re(regexp); const PackagesList &packages = d->remotePackages(); + if (!fetchAllPackages(packages, LocalPackagesHash())) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "There was a problem with loading the package data."; + return; + } PackagesList matchedPackages; foreach (Package *package, packages) { const QString name = package->data(scName).toString(); - if (re.match(name).hasMatch() && - (virtualComponentsVisible() ? true : !package->data(scVirtual, false).toBool())) { - matchedPackages.append(package); + Component *component = componentByName(name); + if (!component) + continue; + + const QModelIndex &idx = model->indexFromComponentName(component->treeName()); + if (idx.isValid() && re.match(name).hasMatch()) { + bool ignoreComponent = false; + for (auto &key : filters.keys()) { + const QString elementValue = component->value(key); + QRegularExpression elementRegexp(filters.value(key)); + if (elementValue.isEmpty() || !elementRegexp.match(elementValue).hasMatch()) { + ignoreComponent = true; + break; + } + } + if (!ignoreComponent) + matchedPackages.append(package); } } if (matchedPackages.count() == 0) @@ -2279,7 +2301,7 @@ bool PackageManagerCore::checkComponentsForInstallation(const QStringList &compo errorMessage.append(tr("Cannot install component %1. Component is installed only as automatic " "dependency to %2.\n").arg(name, component->autoDependencies().join(QLatin1Char(',')))); } else if (!component->isCheckable()) { - errorMessage.append(tr("Cannot install component %1. Component is not checkable meaning you " + errorMessage.append(tr("Cannot install component %1. Component is not checkable, meaning you " "have to select one of the subcomponents.\n").arg(name)); } } else if (component->isInstalled()) { @@ -2288,10 +2310,26 @@ bool PackageManagerCore::checkComponentsForInstallation(const QStringList &compo model->setData(idx, Qt::Checked, Qt::CheckStateRole); installComponentsFound = true; } - } else { // idx is invalid and component valid when we have invisible virtual component - component->isVirtual() - ? errorMessage.append(tr("Cannot install %1. Component is virtual.\n").arg(name)) - : errorMessage.append(tr("Cannot install %1. Component not found.\n").arg(name)); + } else { + auto isDescendantOfVirtual = [&]() { + Component *trace = component; + forever { + trace = trace->parentComponent(); + if (!trace) { + // We already checked the root component if there is no parent + return false; + } else if (trace->isVirtual()) { + errorMessage.append(tr("Cannot install %1. Component is a descendant " + "of a virtual component %2.\n").arg(name, trace->name())); + return true; + } + } + }; + // idx is invalid and component valid when we have invisible virtual component + if (component->isVirtual()) + errorMessage.append(tr("Cannot install %1. Component is virtual.\n").arg(name)); + else if (!isDescendantOfVirtual()) + errorMessage.append(tr("Cannot install %1. Component not found.\n").arg(name)); } } if (!installComponentsFound) @@ -2305,6 +2343,7 @@ bool PackageManagerCore::checkComponentsForInstallation(const QStringList &compo */ void PackageManagerCore::listInstalledPackages(const QString ®exp) { + setPackageViewer(); LocalPackagesHash installedPackages = this->localInstalledPackages(); if (!regexp.isEmpty()) { @@ -2342,6 +2381,7 @@ PackageManagerCore::Status PackageManagerCore::updateComponentsSilently(const QS if (componentList.count() == 0) { qCDebug(QInstaller::lcInstallerInstallLog) << "No updates available."; + setCanceled(); } else { // Check if essential components are available (essential components are disabled). // If essential components are found, update first essential updates, @@ -2703,21 +2743,21 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const if (tempOnSameVolume && (installVolumeAvailableSize <= (required + tempRequired))) { message = tr("Not enough disk space to store temporary files and the " - "installation. %1 are available, while %2 are at least required.").arg( + "installation. %1 are available, while the minimum required is %2.").arg( humanReadableSize(installVolumeAvailableSize), humanReadableSize(required + tempRequired)); return false; } if (installVolumeAvailableSize < required) { message = tr("Not enough disk space to store all selected components! %1 are " - "available while %2 are at least required.").arg(humanReadableSize(installVolumeAvailableSize), + "available, while the minimum required is %2.").arg(humanReadableSize(installVolumeAvailableSize), humanReadableSize(required)); return false; } if (tempVolumeAvailableSize < tempRequired) { - message = tr("Not enough disk space to store temporary files! %1 are available " - "while %2 are at least required.").arg(humanReadableSize(tempVolumeAvailableSize), + message = tr("Not enough disk space to store temporary files! %1 are available, " + "while the minimum required is %2.").arg(humanReadableSize(tempVolumeAvailableSize), humanReadableSize(tempRequired)); return false; } @@ -3402,11 +3442,11 @@ bool PackageManagerCore::isPackageManager() const } /*! - Sets current installer to be offline generator based on \a offlineGenerator. + Sets current installer to be offline generator. */ -void PackageManagerCore::setOfflineGenerator(bool offlineGenerator) +void PackageManagerCore::setOfflineGenerator() { - d->m_offlineGenerator = offlineGenerator; + d->m_magicMarkerSupplement = BinaryContent::OfflineGenerator; } /*! @@ -3420,6 +3460,24 @@ bool PackageManagerCore::isOfflineGenerator() const } /*! + Sets the current installer as the package viewer. +*/ +void PackageManagerCore::setPackageViewer() +{ + d->m_magicMarkerSupplement = BinaryContent::PackageViewer; +} + +/*! + Returns \c true if the current installer is executed as package viewer. + + \sa {installer::isPackageViewer}{installer.isPackageViewer} +*/ +bool PackageManagerCore::isPackageViewer() const +{ + return d->isPackageViewer(); +} + +/*! Sets the installer magic binary marker based on \a magicMarker and userSetBinaryMarker to \c true. */ @@ -3660,7 +3718,7 @@ void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &co qCWarning(QInstaller::lcDeveloperBuild) << componentName << "- Does not exist in the repositories anymore."; continue; } - if (!componentToReplace && !d->componentsToReplace().contains(componentName)) { + if (!d->componentsToReplace().contains(componentName)) { componentToReplace = new Component(this); componentToReplace->setValue(scName, componentName); } else { @@ -3799,10 +3857,8 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const continue; // Update for not installed package found, skip it. const LocalPackage &localPackage = locals.value(name); - const QString updateVersion = update->data(scVersion).toString(); - if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0) + if (!d->packageNeedsUpdate(localPackage, update)) continue; - // It is quite possible that we may have already installed the update. Lets check the last // update date of the package and the release date of the update. This way we can compare and // figure out if the update has been installed or not. @@ -3929,7 +3985,7 @@ void PackageManagerCore::updateDisplayVersions(const QString &displayKey) const QString displayVersionRemote = findDisplayVersion(key, componentsHash, scVersion, visited); if (displayVersionRemote.isEmpty()) - componentsHash.value(key)->setValue(displayKey, tr("invalid")); + componentsHash.value(key)->setValue(displayKey, tr("Invalid")); else componentsHash.value(key)->setValue(displayKey, displayVersionRemote); } diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index d4833a979..b5ef6304e 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -33,6 +33,7 @@ #include "repository.h" #include "qinstallerglobal.h" #include "utils.h" +#include "commandlineparser.h" #include <QtCore/QHash> #include <QtCore/QObject> @@ -242,7 +243,8 @@ public: ComponentModel *defaultComponentModel() const; ComponentModel *updaterComponentModel() const; void listInstalledPackages(const QString ®exp = QString()); - void listAvailablePackages(const QString ®exp); + void listAvailablePackages(const QString ®exp = QString(), + const QHash<QString, QString> &filters = QHash<QString, QString>()); PackageManagerCore::Status updateComponentsSilently(const QStringList &componentsToUpdate); PackageManagerCore::Status installSelectedComponentsSilently(const QStringList& components); PackageManagerCore::Status installDefaultComponentsSilently(); @@ -264,9 +266,12 @@ public: Q_INVOKABLE void setPackageManager(); Q_INVOKABLE bool isPackageManager() const; - void setOfflineGenerator(bool offlineGenerator = true); + void setOfflineGenerator(); Q_INVOKABLE bool isOfflineGenerator() const; + void setPackageViewer(); + Q_INVOKABLE bool isPackageViewer() const; + void setUserSetBinaryMarker(qint64 magicMarker); Q_INVOKABLE bool isUserSetBinaryMarker() const; diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 270cd7760..51c044356 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -211,7 +211,6 @@ static void deferredRename(const QString &oldName, const QString &newName, bool PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) : m_updateFinder(nullptr) - , m_compressedFinder(nullptr) , m_localPackageHub(std::make_shared<LocalPackageHub>()) , m_status(PackageManagerCore::Unfinished) , m_needsHardRestart(false) @@ -225,6 +224,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) , m_repoFetched(false) , m_updateSourcesAdded(false) , m_magicBinaryMarker(0) // initialize with pseudo marker + , m_magicMarkerSupplement(BinaryContent::Default) , m_componentsToInstallCalculated(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) @@ -243,14 +243,12 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) , m_autoAcceptLicenses(false) , m_disableWriteMaintenanceTool(false) , m_autoConfirmCommand(false) - , m_offlineGenerator(false) { } PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, const QList<OperationBlob> &performedOperations) : m_updateFinder(nullptr) - , m_compressedFinder(nullptr) , m_localPackageHub(std::make_shared<LocalPackageHub>()) , m_status(PackageManagerCore::Unfinished) , m_needsHardRestart(false) @@ -264,6 +262,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q , m_repoFetched(false) , m_updateSourcesAdded(false) , m_magicBinaryMarker(magicInstallerMaker) + , m_magicMarkerSupplement(BinaryContent::Default) , m_componentsToInstallCalculated(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) @@ -282,7 +281,6 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q , m_autoAcceptLicenses(false) , m_disableWriteMaintenanceTool(false) , m_autoConfirmCommand(false) - , m_offlineGenerator(false) { foreach (const OperationBlob &operation, performedOperations) { QScopedPointer<QInstaller::Operation> op(KDUpdater::UpdateOperationFactory::instance() @@ -717,7 +715,12 @@ bool PackageManagerCorePrivate::isPackageManager() const bool PackageManagerCorePrivate::isOfflineGenerator() const { - return m_offlineGenerator; + return m_magicMarkerSupplement == BinaryContent::OfflineGenerator; +} + +bool PackageManagerCorePrivate::isPackageViewer() const +{ + return m_magicMarkerSupplement == BinaryContent::PackageViewer; } bool PackageManagerCorePrivate::statusCanceledOrFailed() const @@ -2017,7 +2020,7 @@ bool PackageManagerCorePrivate::runUninstaller() m_core->dropAdminRights(); ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QString::fromLatin1("\n%1").arg( - success ? tr("Uninstallation completed successfully.") : tr("Uninstallation aborted."))); + success ? tr("Removal completed successfully.") : tr("Removal aborted."))); emit uninstallationFinished(); return success; @@ -2251,7 +2254,8 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr component->value(scUncompressedSize).toULongLong(), component->value(scInheritVersion), component->isCheckable(), - component->isExpandedByDefault()); + component->isExpandedByDefault(), + component->value(scContentSha1)); m_localPackageHub->writeToDisk(); component->setInstalled(); @@ -2308,7 +2312,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool() QLatin1String("uninstall.vbs")).absoluteFilePath()); QFile f(batchfile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) - throw Error(tr("Cannot prepare uninstall")); + throw Error(tr("Cannot prepare removal")); QTextStream batch(&f); batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n"; @@ -2339,7 +2343,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool() } if (!QProcessWrapper::startDetached(QLatin1String("cscript"), arguments, QDir::rootPath())) - throw Error(tr("Cannot start uninstall")); + throw Error(tr("Cannot start removal")); #else // every other platform has no problem if we just delete ourselves now QFile maintenanceTool(QFileInfo(installerBinaryPath()).absoluteFilePath()); @@ -2437,7 +2441,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera const QMessageBox::StandardButton button = MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(), QLatin1String("installationErrorWithIgnore"), tr("Installer Error"), - tr("Error during uninstallation process:\n%1").arg(undoOperation->errorString()), + tr("Error during removal process:\n%1").arg(undoOperation->errorString()), QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Ignore); if (button == QMessageBox::Retry) @@ -2480,7 +2484,7 @@ PackagesList PackageManagerCorePrivate::remotePackages() m_updateFinder = new KDUpdater::UpdateFinder; m_updateFinder->setAutoDelete(false); - m_updateFinder->setPackageSources(m_packageSources); + m_updateFinder->setPackageSources(m_packageSources + m_compressedPackageSources); m_updateFinder->setLocalPackageHub(m_localPackageHub); m_updateFinder->run(); @@ -2494,29 +2498,6 @@ PackagesList PackageManagerCorePrivate::remotePackages() return m_updateFinder->updates(); } -PackagesList PackageManagerCorePrivate::compressedPackages() -{ - if (m_compressedUpdates && m_compressedFinder) - return m_compressedFinder->updates(); - m_compressedUpdates = false; - delete m_compressedFinder; - - m_compressedFinder = new KDUpdater::UpdateFinder; - m_compressedFinder->setAutoDelete(false); - m_compressedFinder->addCompressedPackage(true); - m_compressedFinder->setPackageSources(m_compressedPackageSources); - - m_compressedFinder->setLocalPackageHub(m_localPackageHub); - m_compressedFinder->run(); - if (m_compressedFinder->updates().isEmpty()) { - setStatus(PackageManagerCore::Failure, tr("Cannot retrieve remote tree %1.") - .arg(m_compressedFinder->errorString())); - return PackagesList(); - } - m_compressedUpdates = true; - return m_compressedFinder->updates(); -} - /*! Returns a hash containing the installed package name and it's associated package information. If the application is running in installer mode or the local components file could not be parsed, the @@ -2584,42 +2565,6 @@ bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories(DownloadTyp return m_repoFetched; } -bool PackageManagerCorePrivate::fetchMetaInformationFromCompressedRepositories() -{ - bool compressedRepoFetched = false; - - m_compressedUpdates = false; - m_updateSourcesAdded = false; - - try { - //Tell MetadataJob that only compressed packages needed to be fetched and not all. - //We cannot do this in general fetch meta method as the compressed packages might be - //installed after components tree is generated - m_metadataJob.addDownloadType(DownloadType::CompressedPackage); - m_metadataJob.start(); - m_metadataJob.waitForFinished(); - } catch (Error &error) { - setStatus(PackageManagerCore::Failure, tr("Cannot retrieve meta information: %1") - .arg(error.message())); - return compressedRepoFetched; - } - - if (m_metadataJob.error() != Job::NoError) { - switch (m_metadataJob.error()) { - case QInstaller::UserIgnoreError: - break; // we can simply ignore this error, the user knows about it - default: - //Do not change core status here, we can recover if there is invalid - //compressed repository - setStatus(m_core->status(), m_metadataJob.errorString()); - return compressedRepoFetched; - } - } - - compressedRepoFetched = true; - return compressedRepoFetched; -} - bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChecksum, bool compressedRepository) { if (!compressedRepository && m_updateSourcesAdded) @@ -2679,8 +2624,8 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe if (!checksum.isNull()) m_core->setTestChecksum(checksum.toElement().text().toLower() == scTrue); } - if (compressedRepository) - m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 1)); + if (data.repository.isCompressed()) + m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 2)); else m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 1)); @@ -2913,4 +2858,19 @@ bool PackageManagerCorePrivate::askUserConfirmCommand() const } } +bool PackageManagerCorePrivate::packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const +{ + bool updateNeeded = true; + const QString contentSha1 = update->data(scContentSha1).toString(); + if (!contentSha1.isEmpty()) { + if (contentSha1 == localPackage.contentSha1) + updateNeeded = false; + } else { + const QString updateVersion = update->data(scVersion).toString(); + if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0) + updateNeeded = false; + } + return updateNeeded; +} + } // namespace QInstaller diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index 858baf9eb..65f0e43eb 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -132,6 +132,8 @@ public: bool runOfflineGenerator(); bool isOfflineGenerator() const; + bool isPackageViewer() const; + QString replaceVariables(const QString &str) const; QByteArray replaceVariables(const QByteArray &str) const; @@ -175,7 +177,6 @@ signals: public: UpdateFinder *m_updateFinder; - UpdateFinder *m_compressedFinder; QSet<PackageSource> m_packageSources; QSet<PackageSource> m_compressedPackageSources; std::shared_ptr<LocalPackageHub> m_localPackageHub; @@ -242,10 +243,8 @@ private: bool adminRightsGained, bool deleteOperation); PackagesList remotePackages(); - PackagesList compressedPackages(); LocalPackagesHash localInstalledPackages(); bool fetchMetaInformationFromRepositories(DownloadType type = DownloadType::All); - bool fetchMetaInformationFromCompressedRepositories(); bool addUpdateResourcesFromRepositories(bool parseChecksum, bool compressedRepository = false); void processFilesForDelayedDeletion(); void findExecutablesRecursive(const QString &path, const QStringList &excludeFiles, QStringList *result); @@ -254,19 +253,20 @@ private: bool acceptLicenseAgreements() const; bool askUserAcceptLicense(const QString &name, const QString &content) const; bool askUserConfirmCommand() const; + bool packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const; private: PackageManagerCore *m_core; MetadataJob m_metadataJob; bool m_updates; - bool m_compressedUpdates; bool m_repoFetched; bool m_updateSourcesAdded; qint64 m_magicBinaryMarker; + int m_magicMarkerSupplement; + bool m_componentsToInstallCalculated; - bool m_foundEssentialUpdate; - bool m_offlineGenerator; + bool m_foundEssentialUpdate;; mutable ScriptEngine *m_componentScriptEngine; mutable ScriptEngine *m_controlScriptEngine; diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp index 52b0bb222..67a2123af 100644 --- a/src/libs/installer/packagemanagergui.cpp +++ b/src/libs/installer/packagemanagergui.cpp @@ -321,7 +321,7 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent) QFile sheet(styleSheetFile); if (sheet.exists()) { if (sheet.open(QIODevice::ReadOnly)) { - setStyleSheet(QString::fromLatin1(sheet.readAll())); + qApp->setStyleSheet(QString::fromLatin1(sheet.readAll())); } else { qCWarning(QInstaller::lcDeveloperBuild) << "The specified style sheet file " "can not be opened."; @@ -945,7 +945,7 @@ void PackageManagerGui::cancelButtonClicked() interrupt = true; question = tr("Do you want to cancel the installation process?"); if (m_core->isUninstaller()) - question = tr("Do you want to cancel the uninstallation process?"); + question = tr("Do you want to cancel the removal process?"); } else { question = tr("Do you want to quit the installer application?"); if (m_core->isUninstaller()) @@ -1621,6 +1621,10 @@ bool IntroductionPage::validatePage() error = QLatin1String("<font color=\"red\">") + error + tr(" Only local package " "management available.") + QLatin1String("</font>"); } + } else if (core->status() == PackageManagerCore::ForceUpdate) { + // replaces the error string from packagemanagercore + error = tr("There is an important update available. Please select '%1' first") + .arg(m_updateComponents->text().remove(QLatin1Char('&'))); } setErrorMessage(error); } @@ -1959,7 +1963,7 @@ LicenseAgreementPage::LicenseAgreementPage(PackageManagerCore *core) layout->addWidget(licenseSplitter); m_acceptCheckBox = new QCheckBox(this); - m_acceptCheckBox->setShortcut(QKeySequence(tr("Alt+A", "agree license"))); + m_acceptCheckBox->setShortcut(QKeySequence(tr("Alt+A", "Agree license"))); m_acceptCheckBox->setObjectName(QLatin1String("AcceptLicenseCheckBox")); ClickForwarder *acceptClickForwarder = new ClickForwarder(m_acceptCheckBox); @@ -2312,7 +2316,7 @@ TargetDirectoryPage::TargetDirectoryPage(PackageManagerCore *core) QPushButton *browseButton = new QPushButton(this); browseButton->setObjectName(QLatin1String("BrowseDirectoryButton")); connect(browseButton, &QAbstractButton::clicked, this, &TargetDirectoryPage::dirRequested); - browseButton->setShortcut(QKeySequence(tr("Alt+R", "browse file system to choose a file"))); + browseButton->setShortcut(QKeySequence(tr("Alt+R", "Browse file system to choose a file"))); browseButton->setText(tr("B&rowse...")); browseButton->setToolTip(TargetDirectoryPage::tr("Browse file system to choose the installation directory.")); hlayout->addWidget(browseButton); diff --git a/src/libs/installer/performinstallationform.cpp b/src/libs/installer/performinstallationform.cpp index 2fb6026cc..a61c0d88b 100644 --- a/src/libs/installer/performinstallationform.cpp +++ b/src/libs/installer/performinstallationform.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -80,6 +80,7 @@ PerformInstallationForm::PerformInstallationForm(QObject *parent) : QObject(parent) , m_progressBar(nullptr) , m_progressLabel(nullptr) + , m_downloadStatus(nullptr) , m_productImagesScrollArea(nullptr) , m_productImagesLabel(nullptr) , m_detailsButton(nullptr) @@ -120,6 +121,8 @@ void PerformInstallationForm::setupUi(QWidget *widget) m_downloadStatus = new QLabel(widget); m_downloadStatus->setObjectName(QLatin1String("DownloadStatus")); m_downloadStatus->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + m_downloadStatus->setWordWrap(true); + m_downloadStatus->setTextFormat(Qt::TextFormat::RichText); topLayout->addWidget(m_downloadStatus); connect(ProgressCoordinator::instance(), &ProgressCoordinator::downloadStatusChanged, this, &PerformInstallationForm::onDownloadStatusChanged); @@ -281,8 +284,7 @@ bool PerformInstallationForm::isShowingDetails() const */ void PerformInstallationForm::onDownloadStatusChanged(const QString &status) { - m_downloadStatus->setText(m_downloadStatus->fontMetrics().elidedText(status, Qt::ElideRight, - m_downloadStatus->width())); + m_downloadStatus->setText(status); } /*! diff --git a/src/libs/installer/qsettingswrapper.cpp b/src/libs/installer/qsettingswrapper.cpp index 56db28d78..f57750bc1 100644 --- a/src/libs/installer/qsettingswrapper.cpp +++ b/src/libs/installer/qsettingswrapper.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -74,9 +74,9 @@ public: Private(const QString &fileName, QSettings::Format format) : m_filename(fileName) + , m_format(format) , settings(fileName, format) { - m_format = format; m_scope = settings.scope(); m_application = settings.applicationName(); m_organization = settings.organizationName(); diff --git a/src/libs/installer/remoteclient_p.h b/src/libs/installer/remoteclient_p.h index ba60bc7ac..9cc679de8 100644 --- a/src/libs/installer/remoteclient_p.h +++ b/src/libs/installer/remoteclient_p.h @@ -133,8 +133,8 @@ public: QCoreApplication::translate("RemoteClient", "Cannot get authorization."), QCoreApplication::translate("RemoteClient", "Cannot get authorization that is needed for continuing the installation.\n\n" - "Please start the setup program as a user with the appropriate rights.\n" - "Or accept the elevation of access rights if being asked."), + "Please start the setup program as a user with the appropriate rights,\n" + "or accept the elevation of access rights if being asked."), QMessageBox::Abort | QMessageBox::Retry, QMessageBox::Abort); if (res == QMessageBox::Retry) started = AdminAuthorization::execute(0, m_serverCommand, m_serverArguments); diff --git a/src/libs/installer/selfrestartoperation.cpp b/src/libs/installer/selfrestartoperation.cpp index 8e53b8201..360dc60b2 100644 --- a/src/libs/installer/selfrestartoperation.cpp +++ b/src/libs/installer/selfrestartoperation.cpp @@ -61,7 +61,7 @@ bool SelfRestartOperation::performOperation() if (!core->isMaintainer()) { setError(UserDefinedError); - setErrorString(tr("Self Restart: Only valid within updater or packagemanager mode.")); + setErrorString(tr("Self Restart: Only valid within updater or package manager mode.")); return false; } diff --git a/src/libs/installer/settingsoperation.cpp b/src/libs/installer/settingsoperation.cpp index 95ba5266d..c5fe8384c 100644 --- a/src/libs/installer/settingsoperation.cpp +++ b/src/libs/installer/settingsoperation.cpp @@ -82,7 +82,7 @@ bool SettingsOperation::checkArguments() if (!possibleMethodValues.contains(method)) { setError(InvalidArguments); setErrorString(tr("Current method argument calling \"%1\" with arguments \"%2\" is not " - "supported. Please use set, remove, add_array_value or remove_array_value.").arg(name(), + "supported. Please use set, remove, add_array_value, or remove_array_value.").arg(name(), arguments().join(QLatin1String("; ")))); return false; } diff --git a/src/libs/installer/utils.cpp b/src/libs/installer/utils.cpp index 59be2171b..7506a13fe 100644 --- a/src/libs/installer/utils.cpp +++ b/src/libs/installer/utils.cpp @@ -351,12 +351,12 @@ static QString qt_create_commandline(const QString &program, const QStringList & // as escaping the quote -- rather put the \ behind the quote: e.g. // rather use "foo"\ than "foo\" QString endQuote(QLatin1Char('\"')); - int i = tmp.length(); - while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) { - --i; + int j = tmp.length(); + while (j > 0 && tmp.at(j - 1) == QLatin1Char('\\')) { + --j; endQuote += QLatin1Char('\\'); } - args += QLatin1String(" \"") + tmp.left(i) + endQuote; + args += QLatin1String(" \"") + tmp.left(j) + endQuote; } else { args += QLatin1Char(' ') + tmp; } |