diff options
Diffstat (limited to 'src/libs/installer')
96 files changed, 3391 insertions, 959 deletions
diff --git a/src/libs/installer/binaryformat.cpp b/src/libs/installer/binaryformat.cpp index 9ed7742db..54af0c310 100644 --- a/src/libs/installer/binaryformat.cpp +++ b/src/libs/installer/binaryformat.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -151,14 +151,21 @@ void Resource::setName(const QByteArray &name) /*! Opens a resource in QIODevice::ReadOnly mode. The function returns \c true - if successful. + if successful. Optionally, \a permissions can be given. */ +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool Resource::open() +#else +bool Resource::open(std::optional<QFile::Permissions> permissions) +#endif { if (isOpen()) return false; - +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) if (!m_file.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { +#else + if (!m_file.open(QIODevice::ReadOnly | QIODevice::Unbuffered, permissions)) { +#endif setErrorString(m_file.errorString()); return false; } diff --git a/src/libs/installer/binaryformat.h b/src/libs/installer/binaryformat.h index 26d510530..e7505a341 100644 --- a/src/libs/installer/binaryformat.h +++ b/src/libs/installer/binaryformat.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -58,7 +58,11 @@ public: Resource(const QString &path, const Range<qint64> &segment); ~Resource(); +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool open(); +#else + bool open(std::optional<QFile::Permissions> permissions = std::nullopt); +#endif void close() override; bool seek(qint64 pos) override; diff --git a/src/libs/installer/binaryformatengine.cpp b/src/libs/installer/binaryformatengine.cpp index 681e6db79..7f00c8d47 100644 --- a/src/libs/installer/binaryformatengine.cpp +++ b/src/libs/installer/binaryformatengine.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -127,9 +127,16 @@ bool BinaryFormatEngine::close() /*! \internal */ +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool BinaryFormatEngine::open(QIODevice::OpenMode mode) +#else +bool BinaryFormatEngine::open(QIODevice::OpenMode mode, std::optional<QFile::Permissions> permissions) +#endif { Q_UNUSED(mode) +#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) + Q_UNUSED(permissions) +#endif return m_resource.isNull() ? false : m_resource->open(); } diff --git a/src/libs/installer/binaryformatengine.h b/src/libs/installer/binaryformatengine.h index bf72e5f1f..9321e9d9c 100644 --- a/src/libs/installer/binaryformatengine.h +++ b/src/libs/installer/binaryformatengine.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -47,7 +47,12 @@ public: bool copy(const QString &newName) override; bool close() override; +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool open(QIODevice::OpenMode mode) override; +#else + bool open(QIODevice::OpenMode mode, + std::optional<QFile::Permissions> permissions = std::nullopt) override; +#endif qint64 pos() const override; qint64 read(char *data, qint64 maxlen) override; bool seek(qint64 offset) override; diff --git a/src/libs/installer/calculatorbase.h b/src/libs/installer/calculatorbase.h index baf51c5b4..351658f99 100644 --- a/src/libs/installer/calculatorbase.h +++ b/src/libs/installer/calculatorbase.h @@ -49,7 +49,8 @@ public: VirtualDependent, // "No dependencies to virtual component" Dependent, // "Added as dependency for %1." / "Removed as dependency component is removed" Automatic, // "Component(s) added as automatic dependencies" / "Removed as autodependency component is removed" - Resolved // "Component(s) that have resolved Dependencies" + Resolved, // "Component(s) that have resolved Dependencies" + Alias // "Components added from selected alias" }; CalculatorBase(PackageManagerCore *core); diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp index a91ab0128..f9e1f663e 100644 --- a/src/libs/installer/commandlineparser.cpp +++ b/src/libs/installer/commandlineparser.cpp @@ -48,7 +48,7 @@ CommandLineParser::CommandLineParser() "headless mode. The installation operations can be invoked with the following commands and " "options. Note that the options marked with \"CLI\" are available in the headless mode only.\n") + QLatin1String("\nCommands:\n") - + indent + QString::fromLatin1("%1, %2 - install default or selected packages - <pkg ...>\n") + + indent + QString::fromLatin1("%1, %2 - install default or selected packages and aliases - <pkg|alias ...>\n") .arg(CommandLineOptions::scInstallShort, CommandLineOptions::scInstallLong) + indent + QString::fromLatin1("%1, %2 - show available updates information on maintenance tool\n") .arg(CommandLineOptions::scCheckUpdatesShort, CommandLineOptions::scCheckUpdatesLong) @@ -56,13 +56,16 @@ CommandLineParser::CommandLineParser() .arg(CommandLineOptions::scUpdateShort, CommandLineOptions::scUpdateLong) + indent + QString::fromLatin1("%1, %2 - uninstall packages and their child components - <pkg ...>\n") .arg(CommandLineOptions::scRemoveShort, CommandLineOptions::scRemoveLong) - + indent + QString::fromLatin1("%1, %2 - list currently installed packages - <regexp>\n") + + indent + QString::fromLatin1("%1, %2 - list currently installed packages - <regexp for pkg>\n") .arg(CommandLineOptions::scListShort, CommandLineOptions::scListLong) - + indent + QString::fromLatin1("%1, %2 - search available packages - <regexp>\n") + + indent + QString::fromLatin1("%1, %2 - search available aliases or packages - <regexp for pkg|alias>\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 + indent + QString::fromLatin1("Note: The --%1 option can be used to specify\n") + .arg(CommandLineOptions::scTypeLong) + + indent + indent + QLatin1String("the content type to search\n") + indent + QString::fromLatin1("%1, %2 - create offline installer from selected packages - <pkg ...>\n") .arg(CommandLineOptions::scCreateOfflineShort, CommandLineOptions::scCreateOfflineLong) + indent + QString::fromLatin1("%1, %2 - clear contents of the local metadata cache\n") @@ -177,6 +180,12 @@ CommandLineParser::CommandLineParser() << CommandLineOptions::scLocalCachePathShort << CommandLineOptions::scLocalCachePathLong, QLatin1String("Sets the path used for local metadata cache. The path must be writable by the current user."), QLatin1String("path"))); + addOption(QCommandLineOption(QStringList() + << CommandLineOptions::scTypeLong, + QLatin1String("[CLI] Sets the type of the given arguments for commands supporting multiple argument types, " + "like \"search\". By default aliases are searched first, and if no matching aliases are found, " + "then packages are searched with the same search pattern."), + QLatin1String("package|alias"))); // Message query options addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptMessageQueryShort diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp index edd03a191..ce76a2927 100644 --- a/src/libs/installer/component.cpp +++ b/src/libs/installer/component.cpp @@ -46,6 +46,9 @@ #include <QtCore/QDirIterator> #include <QtCore/QTranslator> #include <QtCore/QRegularExpression> +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include <QtCore/QTextCodec> +#endif #include <QApplication> #include <QtConcurrentFilter> @@ -61,6 +64,11 @@ #include <private/qv4object_p.h> #include <algorithm> +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) +#include <QJSEngine> +#else +#include <QQmlEngine> +#endif using namespace QInstaller; @@ -244,6 +252,11 @@ Component::Component(PackageManagerCore *core) : d(new ComponentPrivate(core, this)) , m_defaultArchivePath(scTargetDirPlaceholder) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + QJSEngine::setObjectOwnership(this, QJSEngine::CppOwnership); +#else + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); +#endif setPrivate(d); connect(this, &Component::valueChanged, this, &Component::updateModelData); @@ -667,11 +680,11 @@ void Component::loadTranslations(const QDir &directory, const QStringList &qms) continue; // do not load the file if it does not match the UI language } - QScopedPointer<QTranslator> translator(new QTranslator(this)); + std::unique_ptr<QTranslator> translator(new QTranslator(this)); if (translator->load(filename)) { // Do not throw if translator returns false as it may just be an intentionally // empty file. See also QTBUG-31031 - qApp->installTranslator(translator.take()); + qApp->installTranslator(translator.release()); } } } @@ -693,13 +706,13 @@ void Component::loadUserInterfaces(const QDir &directory, const QStringList &uis it.fileName(), file.errorString(), tr(scClearCacheHint), packageManagerCore()->settings().localCachePath())); } - static QUiLoader loader; - loader.setTranslationEnabled(true); - loader.setLanguageChangeEnabled(true); - QWidget *const widget = loader.load(&file, 0); + QUiLoader *const loader = ProductKeyCheck::instance()->uiLoader(); + loader->setTranslationEnabled(true); + loader->setLanguageChangeEnabled(true); + QWidget *const widget = loader->load(&file, 0); if (!widget) { throw Error(tr("Cannot load the requested UI file \"%1\": %2.\n\n%3 \"%4\"").arg( - it.fileName(), loader.errorString(), tr(scClearCacheHint), packageManagerCore()->settings().localCachePath())); + it.fileName(), loader->errorString(), tr(scClearCacheHint), packageManagerCore()->settings().localCachePath())); } d->scriptEngine()->newQObject(widget); d->m_userInterfaces.insert(widget->objectName(), widget); @@ -748,7 +761,9 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar file.fileName(), file.errorString(), tr(scClearCacheHint), packageManagerCore()->settings().localCachePath())); } QTextStream stream(&file); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) stream.setCodec("UTF-8"); +#endif license.insert(scContent, stream.readAll()); d->m_licenses.insert(it.key(), license); } @@ -960,6 +975,12 @@ QStringList Component::archives() const static const QRegularExpression regExp(scCaretSymbol); QString pathString = scInstallerPrefixWithOneArgs.arg(name()); QStringList archivesNameList = QDir(pathString).entryList(); + + // In resources we may have older version of archives, this can happen + // when there is offline installer with same component with lower version + // number and newer version is available online + archivesNameList = archivesNameList.filter(value(scVersion)); + //RegExp "^" means line beginning archivesNameList.replaceInStrings(regExp, pathString); return archivesNameList; @@ -1089,7 +1110,7 @@ OperationList Component::operations(const Operation::OperationGroups &mask) cons licenses.insert(values.at(i).value(scFile).toString(), values.at(i).value(scContent)); } - d->m_licenseOperation->setValue(scLicenses, licenses); + d->m_licenseOperation->setValue(scLicensesValue, licenses); d->m_operations.append(d->m_licenseOperation); } } @@ -1174,9 +1195,6 @@ Operation *Component::createOperation(const QString &operationName, const QStrin return operation; } - if (operation->name() == scDelete) - operation->setValue(scPerformUndo, false); - // Operation can contain variables which are resolved when performing the operation if (operation->requiresUnreplacedVariables()) operation->setArguments(parameters); @@ -1239,7 +1257,7 @@ inline bool convert(QQmlV4Function *func, QStringList *toArgs) QV4::Object *array = val->as<QV4::Object>(); uint length = array->getLength(); for (uint ii = 0; ii < length; ++ii) { - valtmp = array->getIndexed(ii); + valtmp = array->get(ii); *toArgs << valtmp->toQStringNoThrow(); } } else { diff --git a/src/libs/installer/component_p.cpp b/src/libs/installer/component_p.cpp index 7cf47c925..bf3941274 100644 --- a/src/libs/installer/component_p.cpp +++ b/src/libs/installer/component_p.cpp @@ -188,6 +188,7 @@ void ComponentModelHelper::setCheckable(bool checkable) setData(Qt::Unchecked, Qt::CheckStateRole); } changeFlags(checkable, Qt::ItemIsUserCheckable); + m_componentPrivate->m_vars[scCheckable] = checkable ? scTrue : scFalse; } /*! diff --git a/src/libs/installer/componentalias.cpp b/src/libs/installer/componentalias.cpp new file mode 100644 index 000000000..311ca4475 --- /dev/null +++ b/src/libs/installer/componentalias.cpp @@ -0,0 +1,672 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "componentalias.h" + +#include "constants.h" +#include "globals.h" +#include "packagemanagercore.h" +#include "updater.h" + +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> + +namespace QInstaller { + +static const QStringList scPossibleElements { + scName, + scDisplayName, + scDescription, + scVersion, + scVirtual, + scRequiredComponents, + scRequiredAliases, + scOptionalComponents, + scOptionalAliases, + scReleaseDate +}; + +/*! + \inmodule QtInstallerFramework + \class QInstaller::AliasSource + \brief Describes a source for alias declarations. +*/ + +/*! + \enum QInstaller::AliasSource::SourceFileFormat + + This enum type holds the possible file formats for alias source: + + \value Unknown + Invalid or unknown file format. + \value Xml + XML file format. + \value Json + JSON file format. +*/ + +/*! + Constructs an alias source with empty information. +*/ +AliasSource::AliasSource() + : priority(-1) +{} + +/*! + Constructs an alias source with source file format \a aFormat, filename \a aFilename, and priority \a aPriority. +*/ +AliasSource::AliasSource(SourceFileFormat aFormat, const QString &aFilename, int aPriority) + : format(aFormat) + , filename(aFilename) + , priority(aPriority) +{} + +/*! + Copy-constructs an alias source from \a other. +*/ +AliasSource::AliasSource(const AliasSource &other) +{ + format = other.format; + filename = other.filename; + priority = other.priority; +} + +/*! + Returns the hash value for the \a key, using \a seed to seed the calculation. +*/ +hashValue qHash(const AliasSource &key, hashValue seed) +{ + return qHash(key.filename, seed) ^ key.priority; +} + +/*! + Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false. +*/ +bool operator==(const AliasSource &lhs, const AliasSource &rhs) +{ + return lhs.filename == rhs.filename && lhs.priority == rhs.priority && lhs.format == rhs.format; +} + +/*! + \inmodule QtInstallerFramework + \class QInstaller::AliasFinder + \brief Creates component alias objects from parsed alias source files, based + on version and source priorities. +*/ + +/*! + Constructs a new alias finder with \a core as the package manager instance. +*/ +AliasFinder::AliasFinder(PackageManagerCore *core) + : m_core(core) +{ +} + +/*! + Destroys the finder and cleans unreleased results. +*/ +AliasFinder::~AliasFinder() +{ + clear(); +} + +/*! + Runs the finder. Parses the alias source files and creates component alias + objects based on the parsed data. Same alias may be declared in multiple source + files, thus source priority and version information is used to decide which + source is used for creating the alias object. + + Any previous results are cleared when calling this. + + Returns \c true if at least one alias was found, \c false otherwise. +*/ +bool AliasFinder::run() +{ + clear(); + + if (m_sources.isEmpty()) + return false; + + // 1. Parse source files + for (auto &source : qAsConst(m_sources)) { + if (source.format == AliasSource::SourceFileFormat::Unknown) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Unknown alias source format for file:" << source.filename; + continue; + } + if (source.format == AliasSource::SourceFileFormat::Xml) + parseXml(source); + else if (source.format == AliasSource::SourceFileFormat::Json) + parseJson(source); + } + + // 2. Create aliases based on priority & version + for (auto &data : qAsConst(m_aliasData)) { + const QString name = data.value(scName).toString(); + const Resolution resolution = checkPriorityAndVersion(data); + if (resolution == Resolution::KeepExisting) + continue; + + if (resolution == Resolution::RemoveExisting) + delete m_aliases.take(name); + + ComponentAlias *alias = new ComponentAlias(m_core); + AliasData::const_iterator it; + for (it = data.cbegin(); it != data.cend(); ++it) { + if (it.value().canConvert<QString>()) + alias->setValue(it.key(), it.value().toString()); + } + m_aliases.insert(name, alias); + } + + return !m_aliases.isEmpty(); +} + +/*! + Returns a list of the found aliases. +*/ +QList<ComponentAlias *> AliasFinder::aliases() const +{ + return m_aliases.values(); +} + +/*! + Sets the alias sources to look alias information from to \a sources. +*/ +void AliasFinder::setAliasSources(const QSet<AliasSource> &sources) +{ + clear(); + m_sources = sources; +} + +/*! + Clears the results of the finder. +*/ +void AliasFinder::clear() +{ + qDeleteAll(m_aliases); + + m_aliases.clear(); + m_aliasData.clear(); +} + +/*! + Reads an XML file specified by \a filename, and constructs a variant map of + the data for each alias. + + Returns \c true on success, \c false otherwise. +*/ +bool AliasFinder::parseXml(AliasSource source) +{ + QFile file(source.filename); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot open alias definition for reading:" << file.errorString(); + return false; + } + + QString error; + int errorLine; + int errorColumn; + + QDomDocument doc; + if (!doc.setContent(&file, &error, &errorLine, &errorColumn)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot read alias definition document:" << error + << "line:" << errorLine << "column:" << errorColumn; + return false; + } + file.close(); + + const QDomElement root = doc.documentElement(); + const QDomNodeList children = root.childNodes(); + + for (int i = 0; i < children.count(); ++i) { + const QDomElement el = children.at(i).toElement(); + const QString tag = el.tagName(); + if (el.isNull() || tag != scAlias) { + qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << tag; + continue; + } + + AliasData data; + data.insert(QLatin1String("source"), QVariant::fromValue(source)); + + const QDomNodeList c2 = el.childNodes(); + for (int j = 0; j < c2.count(); ++j) { + const QDomElement el2 = c2.at(j).toElement(); + const QString tag2 = el2.tagName(); + if (!scPossibleElements.contains(tag2)) { + qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << tag2; + continue; + } + data.insert(tag2, el2.text()); + } + + m_aliasData.insert(data.value(scName).toString(), data); + } + + return true; +} + +/*! + Reads a JSON file specified by \a source, and constructs a variant map of + the data for each alias. + + Returns \c true on success, \c false otherwise. +*/ +bool AliasFinder::parseJson(AliasSource source) +{ + QFile file(source.filename); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot open alias definition for reading:" << file.errorString(); + return false; + } + + const QByteArray jsonData = file.readAll(); + const QJsonDocument doc(QJsonDocument::fromJson(jsonData)); + const QJsonObject docJsonObject = doc.object(); + + const QJsonArray aliases = docJsonObject.value(QLatin1String("alias-packages")).toArray(); + for (auto &it : aliases) { + AliasData data; + data.insert(QLatin1String("source"), QVariant::fromValue(source)); + + QJsonObject aliasObj = it.toObject(); + for (const auto &key : aliasObj.keys()) { + if (!scPossibleElements.contains(key)) { + qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << key; + continue; + } + + const QJsonValue jsonValue = aliasObj.value(key); + if (key == scRequiredComponents || key == scRequiredAliases + || key == scOptionalComponents || key == scOptionalAliases) { + const QJsonArray requirements = jsonValue.toArray(); + QString requiresString; + + for (const auto &it2 : requirements) { + requiresString.append(it2.toString()); + if (it2 != requirements.last()) + requiresString.append(QLatin1Char(',')); + } + + data.insert(key, requiresString); + } else if (key == scVirtual) { + data.insert(key, QVariant(jsonValue.toBool()))->toString(); + } else { + data.insert(key, jsonValue.toString()); + } + } + + m_aliasData.insert(data.value(scName).toString(), data); + } + + return true; +} + +/*! + Checks whether \a data should be used for creating a new alias object, + based on version and source priority. + + If an alias of the same name exists, always use the one with the higher + version. If the new alias has the same version but a higher + priority, use the new new alias. Otherwise keep the already existing alias. + + Returns the resolution of the check. +*/ +AliasFinder::Resolution AliasFinder::checkPriorityAndVersion(const AliasData &data) const +{ + for (const auto &existingData : m_aliasData.values(data.value(scName).toString())) { + if (existingData == data) + continue; + + const int versionMatch = KDUpdater::compareVersion(data.value(scVersion).toString(), + existingData.value(scVersion).toString()); + + const AliasSource newSource = data.value(QLatin1String("source")).value<AliasSource>(); + const AliasSource oldSource = existingData.value(QLatin1String("source")).value<AliasSource>(); + + if (versionMatch > 0) { + // new alias has higher version, use + qCDebug(QInstaller::lcDeveloperBuild).nospace() << "Remove Alias 'Name: " + << data.value(scName).toString() << ", Version: " << existingData.value(scVersion).toString() + << ", Source: " << oldSource.filename + << "' found an alias with higher version 'Name: " + << data.value(scName).toString() << ", Version: " << data.value(scVersion).toString() + << ", Source: " << newSource.filename << "'"; + + return Resolution::RemoveExisting; + } + + if ((versionMatch == 0) && (newSource.priority > oldSource.priority)) { + // new alias version equals but priority is higher, use + qCDebug(QInstaller::lcDeveloperBuild).nospace() << "Remove Alias 'Name: " + << data.value(scName).toString() << ", Priority: " << oldSource.priority + << ", Source: " << oldSource.filename + << "' found an alias with higher priority 'Name: " + << data.value(scName).toString() << ", Priority: " << newSource.priority + << ", Source: " << newSource.filename << "'"; + + return Resolution::RemoveExisting; + } + + return Resolution::KeepExisting; // otherwise keep existing + } + + return Resolution::AddNew; +} + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ComponentAlias + \brief The ComponentAlias class represents an alias for single or multiple components. +*/ + +/*! + \enum QInstaller::ComponentAlias::UnstableError + + This enum type holds the possible reasons for marking an alias unstable: + + \value ReferenceToUnstable + Alias requires another alias that is marked unstable. + \value MissingComponent + Alias requires a component that is missing. + \value UnselectableComponent + Alias requires a component that cannot be selected. + \value MissingAlias + Alias requires another alias that is missing. + \value ComponentNameConfict + Alias has a name that conflicts with a name of a component +*/ + +/*! + Constructs a new component alias with \a core as the package manager instance. +*/ +ComponentAlias::ComponentAlias(PackageManagerCore *core) + : m_core(core) + , m_selected(false) + , m_unstable(false) + , m_missingOptionalComponents (false) +{ +} + +/*! + Destructs the alias. +*/ +ComponentAlias::~ComponentAlias() +{ +} + +/*! + Returns the name of the alias. +*/ +QString ComponentAlias::name() const +{ + return m_variables.value(scName); +} + +/*! + Returns the display name of the alias. +*/ +QString ComponentAlias::displayName() const +{ + return m_variables.value(scDisplayName); +} + +/*! + Returns the description text of the alias. +*/ +QString ComponentAlias::description() const +{ + return m_variables.value(scDescription); +} + +/*! + Returns the version of the alias. +*/ +QString ComponentAlias::version() const +{ + return m_variables.value(scVersion); +} + +/*! + Returns \c true if the alias is virtual, \c false otherwise. + + Virtual aliases are aliases that cannot be selected by the + user, and are invisible. They can be required by other aliases however. +*/ +bool ComponentAlias::isVirtual() const +{ + return m_variables.value(scVirtual, scFalse).toLower() == scTrue; +} + +/*! + Returns \c true if the alias is selected for installation, \c false otherwise. +*/ +bool ComponentAlias::isSelected() const +{ + return m_selected; +} + +/*! + Sets the selection state of the alias to \a selected. The selection + does not have an effect if the alias is unselectable. +*/ +void ComponentAlias::setSelected(bool selected) +{ + if (selected && (isUnstable() || isVirtual())) + return; + + m_selected = selected; +} + +/*! + Returns the list of components required by this alias, or an + empty list if this alias does not require any components. +*/ +QList<Component *> ComponentAlias::components() +{ + if (m_components.isEmpty()) { + m_componentErrorMessages.clear(); + m_missingOptionalComponents = false; + const QStringList componentList = QInstaller::splitStringWithComma( + m_variables.value(scRequiredComponents)); + + const QStringList optionalComponentList = QInstaller::splitStringWithComma( + m_variables.value(scOptionalComponents)); + + addRequiredComponents(componentList, false); + addRequiredComponents(optionalComponentList, true); + } + + return m_components; +} + +/*! + Returns the list of other aliases required by this alias, or an + empty list if this alias does not require any other aliases. +*/ +QList<ComponentAlias *> ComponentAlias::aliases() +{ + if (m_aliases.isEmpty()) { + const QStringList aliasList = QInstaller::splitStringWithComma( + m_variables.value(scRequiredAliases)); + + const QStringList optionalAliasList = QInstaller::splitStringWithComma( + m_variables.value(scOptionalAliases)); + + addRequiredAliases(aliasList, false); + addRequiredAliases(optionalAliasList, true); + } + + return m_aliases; +} + +/*! + Returns the value specified by \a key, with an optional default value \a defaultValue. +*/ +QString ComponentAlias::value(const QString &key, const QString &defaultValue) const +{ + return m_variables.value(key, defaultValue); +} + +/*! + Sets the value specified by \a key to \a value. If the value exists already, + it is replaced with the new value. +*/ +void ComponentAlias::setValue(const QString &key, const QString &value) +{ + const QString normalizedValue = m_core->replaceVariables(value); + if (m_variables.value(key) == normalizedValue) + return; + + m_variables[key] = normalizedValue; +} + +/*! + Returns all keys for the component alias values. +*/ +QStringList ComponentAlias::keys() const +{ + return m_variables.keys(); +} + +/*! + Returns \c true if the alias is marked unstable, \c false otherwise. +*/ +bool ComponentAlias::isUnstable() const +{ + return m_unstable; +} + +/*! + Sets the alias unstable with \a error, and a \a message describing the error. +*/ +void ComponentAlias::setUnstable(UnstableError error, const QString &message) +{ + setSelected(false); + m_unstable = true; + + const QMetaEnum metaEnum = QMetaEnum::fromType<ComponentAlias::UnstableError>(); + emit m_core->unstableComponentFound( + QLatin1String(metaEnum.valueToKey(error)), message, name()); +} + +QString ComponentAlias::componentErrorMessage() const +{ + return m_componentErrorMessages; +} + +bool ComponentAlias::missingOptionalComponents() const +{ + return m_missingOptionalComponents; +} + +/*! + \internal + + Adds the \a aliases to the list of required aliases by this alias. If \a optional + is \c true, missing alias references are ignored. +*/ +void ComponentAlias::addRequiredAliases(const QStringList &aliases, const bool optional) +{ + for (const auto &aliasName : aliases) { + ComponentAlias *alias = m_core->aliasByName(aliasName); + if (!alias) { + if (optional) + continue; + + const QString error = QLatin1String("No required alias found by name: ") + aliasName; + qCWarning(lcInstallerInstallLog) << error; + + setUnstable(UnstableError::MissingAlias, error); + continue; + } + + if (alias->isUnstable()) { + if (optional) + continue; + const QString error = QLatin1String("Alias requires another alias " + "that is marked unstable: ") + aliasName; + qCWarning(lcInstallerInstallLog) << error; + + setUnstable(UnstableError::ReferenceToUnstable, error); + continue; + } + + m_aliases.append(alias); + } +} + +/*! + \internal + + Adds the \a components to the list of required components by this alias. If \a optional + is \c true, missing component references are ignored. +*/ +void ComponentAlias::addRequiredComponents(const QStringList &components, const bool optional) +{ + for (const auto &componentName : components) { + Component *component = m_core->componentByName(componentName); + if (!component) { + if (optional) { + m_missingOptionalComponents = true; + continue; + } + + const QString error = QLatin1String("No required component found by name: ") + + componentName; + if (!m_componentErrorMessages.isEmpty()) + m_componentErrorMessages.append(QLatin1String("\n")); + m_componentErrorMessages.append(error); + + setUnstable(UnstableError::MissingComponent, error); + continue; + } + + if (component->isUnstable() || !component->isCheckable()) { + if (optional) + continue; + const QString error = QLatin1String("Alias requires component that is uncheckable or unstable: ") + + componentName; + if (!m_componentErrorMessages.isEmpty()) + m_componentErrorMessages.append(QLatin1String("\n")); + m_componentErrorMessages.append(error); + + setUnstable(UnstableError::UnselectableComponent, error); + continue; + } + + m_components.append(component); + } +} + +} // namespace QInstaller diff --git a/src/libs/installer/componentalias.h b/src/libs/installer/componentalias.h new file mode 100644 index 000000000..3083260cd --- /dev/null +++ b/src/libs/installer/componentalias.h @@ -0,0 +1,164 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef COMPONENTALIAS_H +#define COMPONENTALIAS_H + +#include "installer_global.h" + +#include <QHash> +#include <QMetaEnum> +#include <QString> +#include <QSet> + +namespace QInstaller { + +class Component; +class ComponentAlias; +class PackageManagerCore; + +struct INSTALLER_EXPORT AliasSource +{ + enum class SourceFileFormat { + Unknown = -1, + Xml = 0, + Json + }; + + AliasSource(); + AliasSource(SourceFileFormat aFormat, const QString &aFilename, int aPriority); + AliasSource(const AliasSource &other); + + SourceFileFormat format; + QString filename; + int priority; +}; + +INSTALLER_EXPORT hashValue qHash(const AliasSource &key, hashValue seed); +INSTALLER_EXPORT bool operator==(const AliasSource &lhs, const AliasSource &rhs); + +class INSTALLER_EXPORT AliasFinder +{ +public: + using AliasData = QVariantMap; + using AliasDataHash = QMultiHash<QString, AliasData>; + + enum struct Resolution { + AddNew, + KeepExisting, + RemoveExisting + }; + + explicit AliasFinder(PackageManagerCore *core); + ~AliasFinder(); + + bool run(); + QList<ComponentAlias *> aliases() const; + + void setAliasSources(const QSet<AliasSource> &sources); + +private: + void clear(); + Resolution checkPriorityAndVersion(const AliasData &data) const; + + bool parseXml(AliasSource source); + bool parseJson(AliasSource source); + +private: + PackageManagerCore *const m_core; + + QSet<AliasSource> m_sources; + AliasDataHash m_aliasData; + QHash<QString, ComponentAlias *> m_aliases; +}; + +class INSTALLER_EXPORT ComponentAlias : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ComponentAlias) + +public: + enum UnstableError { + ReferenceToUnstable = 0, + MissingComponent, + UnselectableComponent, + MissingAlias, + ComponentNameConfict + }; + Q_ENUM(UnstableError) + + ComponentAlias(PackageManagerCore *core); + ~ComponentAlias(); + + QString name() const; + QString displayName() const; + QString description() const; + + QString version() const; + + bool isVirtual() const; + + bool isSelected() const; + void setSelected(bool selected); + + QList<Component *> components(); + QList<ComponentAlias *> aliases(); + + QString value(const QString &key, const QString &defaultValue = QString()) const; + void setValue(const QString &key, const QString &value); + QStringList keys() const; + + bool isUnstable() const; + void setUnstable(UnstableError error, const QString &message = QString()); + QString componentErrorMessage() const; + bool missingOptionalComponents() const; + +private: + void addRequiredAliases(const QStringList &aliases, const bool optional); + void addRequiredComponents(const QStringList &components, const bool optional); + +private: + PackageManagerCore *const m_core; + + QHash<QString, QString> m_variables; + + bool m_selected; + bool m_unstable; + + QList<Component *> m_components; + bool m_missingOptionalComponents; + QList<ComponentAlias *> m_aliases; + QString m_componentErrorMessages; +}; + +} // namespace QInstaller + +Q_DECLARE_METATYPE(QInstaller::ComponentAlias *) +Q_DECLARE_METATYPE(QInstaller::AliasSource); + +#endif // COMPONENTALIAS_H diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp index dbb80f2f6..1e8dd1ff7 100644 --- a/src/libs/installer/componentmodel.cpp +++ b/src/libs/installer/componentmodel.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -335,6 +335,22 @@ QSet<Component *> ComponentModel::uncheckable() const return m_uncheckable; } +bool ComponentModel::componentsSelected() const +{ + if (m_core->isInstaller() || m_core->isUpdater()) + return checked().count(); + + if (checkedState().testFlag(ComponentModel::DefaultChecked) == false) + return true; + + const QSet<Component *> uncheckables = uncheckable(); + for (auto &component : uncheckables) { + if (component->forcedInstallation() && !component->isInstalled()) + return true; // allow installation for new forced components + } + return false; +} + /*! Returns a pointer to the PackageManagerCore this model belongs to. */ @@ -562,12 +578,11 @@ QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &compone for (int i = sortedNodes.count(); i > 0; i--) { Component * const node = sortedNodes.at(i - 1); - bool checkable = true; - if (node->value(scCheckable, scTrue).toLower() == scFalse) { - checkable = false; - } + if (!node->isEnabled() || node->isUnstable()) + continue; - if ((!node->isCheckable() && checkable) || !node->isEnabled() || node->isUnstable()) + //Do not let forced installations to be uninstalled + if (!m_core->isUpdater() && node->forcedInstallation() && (node->checkState() != Qt::Unchecked)) continue; if (!m_core->isUpdater() && !node->autoDependencies().isEmpty()) diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h index 1e8a2d297..c93dd60ae 100644 --- a/src/libs/installer/componentmodel.h +++ b/src/libs/installer/componentmodel.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -80,6 +80,7 @@ public: QSet<Component *> partially() const; QSet<Component *> unchecked() const; QSet<Component *> uncheckable() const; + bool componentsSelected() const; PackageManagerCore *core() const; ComponentModel::ModelState checkedState() const; diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp index 7f82ef47d..b68eebf06 100644 --- a/src/libs/installer/componentselectionpage_p.cpp +++ b/src/libs/installer/componentselectionpage_p.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -35,6 +35,7 @@ #include "component.h" #include "fileutils.h" #include "messageboxhandler.h" +#include "customcombobox.h" #include <QTreeView> #include <QLabel> @@ -50,7 +51,6 @@ #include <QStackedLayout> #include <QStackedWidget> #include <QLineEdit> -#include <QComboBox> #include <QStandardItemModel> #include <QStyledItemDelegate> @@ -71,22 +71,20 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP : q(qq) , m_core(core) , m_treeView(new QTreeView(q)) - , m_allModel(m_core->defaultComponentModel()) - , m_updaterModel(m_core->updaterComponentModel()) - , m_currentModel(m_allModel) - , m_allowCompressedRepositoryInstall(false) , m_tabWidget(nullptr) , m_descriptionBaseWidget(nullptr) , m_categoryWidget(Q_NULLPTR) + , m_allowCreateOfflineInstaller(false) , m_categoryLayoutVisible(false) - , m_proxyModel(new ComponentSortFilterProxyModel(q)) + , m_allModel(m_core->defaultComponentModel()) + , m_updaterModel(m_core->updaterComponentModel()) + , m_currentModel(m_allModel) + , m_proxyModel(m_core->componentSortFilterProxyModel()) , m_componentsResolved(false) , m_headerStretchLastSection(false) { m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); m_treeView->setUniformRowHeights(true); - m_proxyModel->setRecursiveFilteringEnabled(true); - m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_descriptionBaseWidget = new QWidget(q); m_descriptionBaseWidget->setObjectName(QLatin1String("DescriptionBaseWidget")); @@ -121,10 +119,21 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP m_sizeLabel->setObjectName(QLatin1String("ComponentSizeLabel")); descriptionVLayout->addWidget(m_sizeLabel); + m_createOfflinePushButton = new QPushButton(q); + m_createOfflinePushButton->setVisible(false); + m_createOfflinePushButton->setText(ComponentSelectionPage::tr("Create Offline Installer")); + m_createOfflinePushButton->setToolTip( + ComponentSelectionPage::tr("Create offline installer from selected components, instead " + "of installing now.")); + + connect(m_createOfflinePushButton, &QPushButton::clicked, + this, &ComponentSelectionPagePrivate::createOfflineButtonClicked); + connect(q, &ComponentSelectionPage::completeChanged, + this, [&]() { m_createOfflinePushButton->setEnabled(q->isComplete()); }); + m_qbspPushButton = new QPushButton(q); m_qbspPushButton->setVisible(false); m_qbspPushButton->setText(ComponentSelectionPage::tr("Browse &QBSP files")); - m_qbspPushButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_qbspPushButton->setToolTip( ComponentSelectionPage::tr("Select a Qt Board Support Package file to install " "additional content that is not directly available from the online repositories.")); @@ -133,18 +142,28 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP this, &ComponentSelectionPagePrivate::qbspButtonClicked); m_rightSideVLayout->addWidget(m_descriptionBaseWidget); - m_rightSideVLayout->addWidget(m_qbspPushButton, 0, Qt::AlignRight | Qt::AlignBottom); + m_rightSideVLayout->addWidget(m_createOfflinePushButton); + m_rightSideVLayout->addWidget(m_qbspPushButton); QHBoxLayout *topHLayout = new QHBoxLayout; - m_checkStateComboBox = new QComboBox(q); + + // Using custom combobox to workaround QTBUG-90595 + m_checkStateComboBox = new CustomComboBox(q); +#ifdef Q_OS_MACOS QStyledItemDelegate *delegate = new QStyledItemDelegate(this); m_checkStateComboBox->setItemDelegate(delegate); +#endif m_checkStateComboBox->setObjectName(QLatin1String("CheckStateComboBox")); topHLayout->addWidget(m_checkStateComboBox); connect(m_checkStateComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ComponentSelectionPagePrivate::updateAllCheckStates); + // Workaround invisible placeholder text + QPalette palette = m_checkStateComboBox->palette(); + palette.setColor(QPalette::PlaceholderText, palette.color(QPalette::Text)); + m_checkStateComboBox->setPalette(palette); + m_checkStateComboBox->setPlaceholderText(ComponentSelectionPage::tr("Select")); if (m_core->isInstaller()) { m_checkStateComboBox->insertItem(scCheckDefaultIndex, ComponentSelectionPage::tr("Default")); @@ -215,10 +234,6 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP connect(m_core, SIGNAL(metaJobInfoMessage(QString)), this, SLOT(setMessage(QString))); connect(m_core, &PackageManagerCore::metaJobTotalProgress, this, &ComponentSelectionPagePrivate::setTotalProgress); - -#ifdef INSTALLCOMPRESSED - allowCompressedRepositoryInstall(); -#endif } ComponentSelectionPagePrivate::~ComponentSelectionPagePrivate() @@ -226,14 +241,14 @@ ComponentSelectionPagePrivate::~ComponentSelectionPagePrivate() } -void ComponentSelectionPagePrivate::allowCompressedRepositoryInstall() +void ComponentSelectionPagePrivate::setAllowCreateOfflineInstaller(bool allow) { - m_allowCompressedRepositoryInstall = true; + m_allowCreateOfflineInstaller = allow; } void ComponentSelectionPagePrivate::showCompressedRepositoryButton() { - if (m_allowCompressedRepositoryInstall) + if (m_core->allowCompressedRepositoryInstall()) m_qbspPushButton->setVisible(true); } @@ -242,6 +257,14 @@ void ComponentSelectionPagePrivate::hideCompressedRepositoryButton() m_qbspPushButton->setVisible(false); } +void ComponentSelectionPagePrivate::showCreateOfflineInstallerButton(bool show) +{ + if (show && m_allowCreateOfflineInstaller) + m_createOfflinePushButton->setVisible(m_core->isInstaller() && !m_core->isOfflineOnly()); + else + m_createOfflinePushButton->setVisible(false); +} + void ComponentSelectionPagePrivate::setupCategoryLayout() { if (m_categoryWidget) @@ -288,11 +311,11 @@ void ComponentSelectionPagePrivate::showCategoryLayout(bool show) if (show) { m_rightSideVLayout->removeWidget(m_descriptionBaseWidget); m_tabWidget->insertTab(0, m_descriptionBaseWidget, tr("Information")); - m_rightSideVLayout->insertWidget(m_rightSideVLayout->count() - 1, m_tabWidget); + m_rightSideVLayout->insertWidget(0, m_tabWidget); } else { m_tabWidget->removeTab(0); m_rightSideVLayout->removeWidget(m_tabWidget); - m_rightSideVLayout->insertWidget(m_rightSideVLayout->count() - 1, m_descriptionBaseWidget); + m_rightSideVLayout->insertWidget(0, m_descriptionBaseWidget); m_descriptionBaseWidget->setVisible(true); } m_tabWidget->setVisible(show); @@ -463,27 +486,6 @@ void ComponentSelectionPagePrivate::deselectAll() m_currentModel->setCheckedState(ComponentModel::AllUnchecked); } -void ComponentSelectionPagePrivate::enableRepositoryCategory(const QString &repositoryName, bool enable) -{ - QMap<QString, RepositoryCategory> organizedRepositoryCategories = m_core->settings().organizedRepositoryCategories(); - - QMap<QString, RepositoryCategory>::iterator i = organizedRepositoryCategories.find(repositoryName); - RepositoryCategory repoCategory; - while (i != organizedRepositoryCategories.end() && i.key() == repositoryName) { - repoCategory = i.value(); - i++; - } - - RepositoryCategory replacement = repoCategory; - replacement.setEnabled(enable); - QSet<RepositoryCategory> tmpRepoCategories = m_core->settings().repositoryCategories(); - if (tmpRepoCategories.contains(repoCategory)) { - tmpRepoCategories.remove(repoCategory); - tmpRepoCategories.insert(replacement); - m_core->settings().addRepositoryCategories(tmpRepoCategories); - } -} - void ComponentSelectionPagePrivate::updateWidgetVisibility(bool show) { if (show) @@ -511,7 +513,7 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories() QList<QCheckBox*> checkboxes = m_categoryGroupBox->findChildren<QCheckBox *>(); for (int i = 0; i < checkboxes.count(); i++) { QCheckBox *checkbox = checkboxes.at(i); - enableRepositoryCategory(checkbox->objectName(), checkbox->isChecked()); + m_core->enableRepositoryCategory(checkbox->objectName(), checkbox->isChecked()); } if (!m_core->fetchRemotePackagesTree()) { @@ -522,6 +524,12 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories() m_searchLineEdit->text().isEmpty() ? expandDefault() : expandSearchResults(); } +void ComponentSelectionPagePrivate::createOfflineButtonClicked() +{ + m_core->setOfflineGenerator(); + q->gui()->button(QWizard::NextButton)->click(); +} + void ComponentSelectionPagePrivate::qbspButtonClicked() { QString defaultDownloadDirectory = @@ -530,15 +538,8 @@ void ComponentSelectionPagePrivate::qbspButtonClicked() ComponentSelectionPage::tr("Open File"),defaultDownloadDirectory, QLatin1String("QBSP or 7z Files (*.qbsp *.7z)")); - QSet<Repository> set; - foreach (QString fileName, fileNames) { - Repository repository = Repository::fromUserInput(fileName, true); - repository.setEnabled(true); - set.insert(repository); - } - if (set.count() > 0) { + if (m_core->addQBspRepositories(fileNames)) { updateWidgetVisibility(true); - m_core->settings().addTemporaryRepositories(set, false); if (!m_core->fetchCompressedPackagesTree()) { MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("FailToFetchPackages"), tr("Error"), m_core->error()); diff --git a/src/libs/installer/componentselectionpage_p.h b/src/libs/installer/componentselectionpage_p.h index ed68fafa4..187fce61d 100644 --- a/src/libs/installer/componentselectionpage_p.h +++ b/src/libs/installer/componentselectionpage_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -48,13 +48,13 @@ class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QStackedLayout; -class QComboBox; namespace QInstaller { class PackageManagerCore; class ComponentModel; class ComponentSelectionPage; +class CustomComboBox; class ComponentSelectionPagePrivate : public QObject { @@ -66,9 +66,10 @@ public: explicit ComponentSelectionPagePrivate(ComponentSelectionPage *qq, PackageManagerCore *core); ~ComponentSelectionPagePrivate(); - void allowCompressedRepositoryInstall(); + void setAllowCreateOfflineInstaller(bool allow); void showCompressedRepositoryButton(); void hideCompressedRepositoryButton(); + void showCreateOfflineInstallerButton(bool show); void setupCategoryLayout(); void showCategoryLayout(bool show); void updateTreeView(); @@ -81,9 +82,9 @@ public slots: void updateAllCheckStates(int which); void selectAll(); void deselectAll(); - void enableRepositoryCategory(const QString &repositoryName, bool enable); void updateWidgetVisibility(bool show); void fetchRepositoryCategories(); + void createOfflineButtonClicked(); void qbspButtonClicked(); void onProgressChanged(int progress); void setMessage(const QString &msg); @@ -105,15 +106,16 @@ private: QWidget *m_descriptionBaseWidget; QLabel *m_sizeLabel; QLabel *m_descriptionLabel; + QPushButton *m_createOfflinePushButton; QPushButton *m_qbspPushButton; - QComboBox *m_checkStateComboBox; + CustomComboBox *m_checkStateComboBox; QWidget *m_categoryWidget; QGroupBox *m_categoryGroupBox; QLabel *m_metadataProgressLabel; QProgressBar *m_progressBar; QGridLayout *m_mainGLayout; QVBoxLayout *m_rightSideVLayout; - bool m_allowCompressedRepositoryInstall; + bool m_allowCreateOfflineInstaller; bool m_categoryLayoutVisible; ComponentModel *m_allModel; ComponentModel *m_updaterModel; diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h index ecd85fe11..7bf816b5f 100644 --- a/src/libs/installer/constants.h +++ b/src/libs/installer/constants.h @@ -60,6 +60,11 @@ static const QLatin1String scDisplayName("DisplayName"); static const QLatin1String scTreeName("TreeName"); static const QLatin1String scAutoTreeName("AutoTreeName"); static const QLatin1String scDependencies("Dependencies"); +static const QLatin1String scAlias("Alias"); +static const QLatin1String scRequiredAliases("RequiredAliases"); +static const QLatin1String scRequiredComponents("RequiredComponents"); +static const QLatin1String scOptionalAliases("OptionalAliases"); +static const QLatin1String scOptionalComponents("OptionalComponents"); static const QLatin1String scLocalDependencies("LocalDependencies"); static const QLatin1String scAutoDependOn("AutoDependOn"); static const QLatin1String scNewComponent("NewComponent"); @@ -75,7 +80,7 @@ static const QLatin1String scMetadataName("MetadataName"); static const QLatin1String scContentSha1("ContentSha1"); static const QLatin1String scCheckSha1CheckSum("CheckSha1CheckSum"); -static const char *scClearCacheHint = QT_TR_NOOP( +static const char * const scClearCacheHint = QT_TR_NOOP( "This may be solved by restarting the application after clearing the cache from:"); // symbols @@ -102,6 +107,7 @@ static const QLatin1String scInstallDate("InstallDate"); static const QLatin1String scUserInterfaces("UserInterfaces"); static const QLatin1String scTranslations("Translations"); static const QLatin1String scLicenses("Licenses"); +static const QLatin1String scLicensesValue("licenses"); static const QLatin1String scLicense("License"); static const QLatin1String scOperations("Operations"); static const QLatin1String scInstallScript("installScript"); @@ -123,7 +129,6 @@ static const QLatin1String scMinimumProgress("MinimumProgress"); static const QLatin1String scDelete("Delete"); static const QLatin1String scCopy("Copy"); static const QLatin1String scMkdir("Mkdir"); -static const QLatin1String scPerformUndo("performUndo"); static const QLatin1String scIsDefault("isDefault"); static const QLatin1String scAdmin("admin"); static const QLatin1String scTwoArgs("%1/%2/"); @@ -152,6 +157,7 @@ static const QLatin1String scRemoteRepositories("RemoteRepositories"); static const QLatin1String scRepositoryCategories("RepositoryCategories"); static const QLatin1String scRepositorySettingsPageVisible("RepositorySettingsPageVisible"); static const QLatin1String scAllowSpaceInPath("AllowSpaceInPath"); +static const QLatin1String scAllowRepositoriesForOfflineInstaller("AllowRepositoriesForOfflineInstaller"); static const QLatin1String scWizardStyle("WizardStyle"); static const QLatin1String scStyleSheet("StyleSheet"); static const QLatin1String scTitleColor("TitleColor"); @@ -174,7 +180,15 @@ static const QLatin1String scBanner("Banner"); static const QLatin1String scLogo("Logo"); static const QLatin1String scBackground("Background"); static const QLatin1String scPageListPixmap("PageListPixmap"); +static const QLatin1String scAliasDefinitionsFile("AliasDefinitionsFile"); const char scRelocatable[] = "@RELOCATABLE_PATH@"; + +static const QStringList scMetaElements = { + QLatin1String("Script"), + QLatin1String("Licenses"), + QLatin1String("UserInterfaces"), + QLatin1String("Translations") +}; } namespace CommandLineOptions { @@ -271,6 +285,7 @@ static const QLatin1String scFilterPackagesShort("fp"); static const QLatin1String scFilterPackagesLong("filter-packages"); static const QLatin1String scLocalCachePathShort("cp"); static const QLatin1String scLocalCachePathLong("cache-path"); +static const QLatin1String scTypeLong("type"); // Developer options static const QLatin1String scScriptShort("s"); diff --git a/src/libs/installer/copydirectoryoperation.cpp b/src/libs/installer/copydirectoryoperation.cpp index a2ef2cf5a..c0fec0649 100644 --- a/src/libs/installer/copydirectoryoperation.cpp +++ b/src/libs/installer/copydirectoryoperation.cpp @@ -153,7 +153,7 @@ bool CopyDirectoryOperation::performOperation() bool CopyDirectoryOperation::undoOperation() { - if (parseUndoOperationArguments().count() > 0) + if (skipUndoOperation()) return true; if (!checkArgumentCount(2)) diff --git a/src/libs/installer/copyfiletask.cpp b/src/libs/installer/copyfiletask.cpp index 72b28d896..856feda01 100644 --- a/src/libs/installer/copyfiletask.cpp +++ b/src/libs/installer/copyfiletask.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -112,7 +112,7 @@ void CopyFileTask::doTask(QFutureInterface<FileTaskResult> &fi) observer.addSample(read); observer.timerEvent(nullptr); observer.addBytesTransfered(read); - observer.addCheckSumData(buffer.data(), read); + observer.addCheckSumData(buffer.left(read)); fi.setProgressValueAndText(observer.progressValue(), observer.progressText()); } diff --git a/src/libs/installer/createdesktopentryoperation.cpp b/src/libs/installer/createdesktopentryoperation.cpp index c3988a8ec..a19fd773a 100644 --- a/src/libs/installer/createdesktopentryoperation.cpp +++ b/src/libs/installer/createdesktopentryoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -32,6 +32,7 @@ #include "globals.h" #include "adminauthorization.h" #include "remoteclient.h" +#include "packagemanagercore.h" #include <QDir> #include <QFile> @@ -64,7 +65,7 @@ QString CreateDesktopEntryOperation::absoluteFileName() XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default user-specific path - if (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()) + if (packageManager() && packageManager()->hasAdminRights()) XDG_DATA_HOME.push_front(QLatin1String("/usr/local/share")); // default system-wide path const QStringList directories = XDG_DATA_HOME; @@ -151,15 +152,16 @@ bool CreateDesktopEntryOperation::performOperation() setDefaultFilePermissions(filename, DefaultFilePermissions::Executable); - QTextStream stream(&file); - stream.setCodec("UTF-8"); - stream << QLatin1String("[Desktop Entry]") << endl; + QString outString; + QTextStream stream(&outString); + stream << QLatin1String("[Desktop Entry]") << Qt::endl; // Type=Application\nExec=qtcreator\nPath=... const QStringList pairs = values.split(QLatin1Char('\n')); for (QStringList::const_iterator it = pairs.begin(); it != pairs.end(); ++it) stream << *it << Qt::endl; + file.write(outString.toUtf8()); return true; } diff --git a/src/libs/installer/createlocalrepositoryoperation.cpp b/src/libs/installer/createlocalrepositoryoperation.cpp index 7090f9a8b..286cc9b5b 100644 --- a/src/libs/installer/createlocalrepositoryoperation.cpp +++ b/src/libs/installer/createlocalrepositoryoperation.cpp @@ -378,7 +378,7 @@ bool CreateLocalRepositoryOperation::performOperation() bool CreateLocalRepositoryOperation::undoOperation() { - if (parseUndoOperationArguments().count() > 0) + if (skipUndoOperation()) return true; if (!checkArgumentCount(2)) diff --git a/src/libs/installer/createshortcutoperation.cpp b/src/libs/installer/createshortcutoperation.cpp index 57f901c2f..894b5843b 100644 --- a/src/libs/installer/createshortcutoperation.cpp +++ b/src/libs/installer/createshortcutoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -110,6 +110,7 @@ static bool createLink(const QString &fileName, const QString &linkName, QString IUnknown *iunkn = nullptr; if (fileName.toLower().startsWith(QLatin1String("http:")) + || fileName.toLower().startsWith(QLatin1String("https:")) || fileName.toLower().startsWith(QLatin1String("ftp:"))) { IUniformResourceLocator *iurl = nullptr; if (FAILED(CoCreateInstance(CLSID_InternetShortcut, nullptr, CLSCTX_INPROC_SERVER, @@ -176,6 +177,7 @@ static bool createLink(const QString &fileName, const QString &linkName, QString Q_UNUSED(linkName) Q_UNUSED(iconPath) Q_UNUSED(iconId) + Q_UNUSED(description) return true; #endif } diff --git a/src/libs/installer/customcombobox.cpp b/src/libs/installer/customcombobox.cpp new file mode 100644 index 000000000..998364fe4 --- /dev/null +++ b/src/libs/installer/customcombobox.cpp @@ -0,0 +1,54 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "customcombobox.h" + +#include <QStylePainter> + +using namespace QInstaller; + +CustomComboBox::CustomComboBox(QWidget *parent) + : QComboBox(parent) +{ +} + +void CustomComboBox::paintEvent(QPaintEvent *e) +{ + if (currentIndex() < 0 && !placeholderText().isEmpty()) { + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + opt.palette.setBrush(QPalette::ButtonText, opt.palette.placeholderText()); + opt.currentText = placeholderText(); + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); + } else { + QComboBox::paintEvent(e); + } +} diff --git a/src/libs/installer/customcombobox.h b/src/libs/installer/customcombobox.h new file mode 100644 index 000000000..e022da5a8 --- /dev/null +++ b/src/libs/installer/customcombobox.h @@ -0,0 +1,48 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef CUSTOMCOMBOBOX_H +#define CUSTOMCOMBOBOX_H + +#include <QComboBox> + +namespace QInstaller { + +class CustomComboBox : public QComboBox +{ + Q_OBJECT +public: + CustomComboBox(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *e) override; +}; + +} // namespace QInstaller + +#endif // CUSTOMCOMBOBOX_H diff --git a/src/libs/installer/downloadarchivesjob.cpp b/src/libs/installer/downloadarchivesjob.cpp index 48700bd12..65eead1f9 100644 --- a/src/libs/installer/downloadarchivesjob.cpp +++ b/src/libs/installer/downloadarchivesjob.cpp @@ -44,10 +44,12 @@ using namespace QInstaller; using namespace KDUpdater; +static constexpr uint scMaxRetries = 5; + /*! Creates a new DownloadArchivesJob with parent \a core. */ -DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core) +DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core, const QString &objectName) : Job(core) , m_core(core) , m_downloader(nullptr) @@ -58,8 +60,10 @@ DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core) , m_progressChangedTimerId(0) , m_totalSizeToDownload(0) , m_totalSizeDownloaded(0) + , m_retryCount(scMaxRetries) { setCapabilities(Cancelable); + setObjectName(objectName); } /*! @@ -289,17 +293,24 @@ void DownloadArchivesJob::registerFile() const QMessageBox::Button res = MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("DownloadError"), tr("Download Error"), tr("Hash verification while " - "downloading failed. This is a temporary error, please retry."), - QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel); - - // If run from command line instance, do not continue if hash verification failed. - // Same download is tried again and again causing infinite loop if hash not - // fixed to repositories. - if (res == QMessageBox::Cancel || m_core->isCommandLineInstance()) { - finishWithError(tr("Cannot verify Hash")); + "downloading failed. This is a temporary error, please retry.\n\n" + "Expected: %1 \nDownloaded: %2").arg(QString::fromLatin1(m_currentHash), QString::fromLatin1(m_downloader->sha1Sum().toHex())), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Retry); + + if (res == QMessageBox::Cancel) { + finishWithError(tr("Cannot verify Hash\nExpected: %1 \nDownloaded: %2") + .arg(QString::fromLatin1(m_currentHash), QString::fromLatin1(m_downloader->sha1Sum().toHex()))); return; } + // When using command line instance, only retry a number of times to avoid + // infinite loop in case the automatic answer for the messagebox is "Retry" + if (m_core->isCommandLineInstance() && (--m_retryCount == 0)) { + finishWithError(tr("Retry count (%1) exceeded").arg(scMaxRetries)); + return; + } } else { + m_retryCount = scMaxRetries; + ++m_archivesDownloaded; m_totalSizeDownloaded += QFile(m_downloader->downloadedFileName()).size(); if (m_progressChangedTimerId) { @@ -330,14 +341,21 @@ void DownloadArchivesJob::downloadFailed(const QString &error) const QMessageBox::StandardButton b = MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("archiveDownloadError"), tr("Download Error"), tr("Cannot download archive %1: %2") - .arg(m_archivesToDownload.first().sourceUrl, error), QMessageBox::Retry | QMessageBox::Cancel); + .arg(m_archivesToDownload.first().sourceUrl, error), QMessageBox::Retry | QMessageBox::Cancel, + QMessageBox::Retry); + + if (b == QMessageBox::Retry) { + // When using command line instance, only retry a number of times to avoid + // infinite loop in case the automatic answer for the messagebox is "Retry" + if (m_core->isCommandLineInstance() && (--m_retryCount == 0)) { + finishWithError(tr("Retry count (%1) exceeded").arg(scMaxRetries)); + return; + } - // Do not call fetchNextArchiveHash when using command line instance, - // installer tries to download the same archive causing infinite loop - if (b == QMessageBox::Retry && !m_core->isCommandLineInstance()) QMetaObject::invokeMethod(this, "fetchNextArchiveHash", Qt::QueuedConnection); - else + } else { downloadCanceled(); + } } void DownloadArchivesJob::finishWithError(const QString &error) diff --git a/src/libs/installer/downloadarchivesjob.h b/src/libs/installer/downloadarchivesjob.h index c156d0244..5155c881a 100644 --- a/src/libs/installer/downloadarchivesjob.h +++ b/src/libs/installer/downloadarchivesjob.h @@ -51,7 +51,7 @@ class DownloadArchivesJob : public Job Q_OBJECT public: - explicit DownloadArchivesJob(PackageManagerCore *core); + explicit DownloadArchivesJob(PackageManagerCore *core, const QString &objectName); ~DownloadArchivesJob(); int numberOfDownloads() const { return m_archivesDownloaded; } @@ -103,6 +103,8 @@ private: quint64 m_totalSizeToDownload; quint64 m_totalSizeDownloaded; QElapsedTimer m_totalDownloadSpeedTimer; + + uint m_retryCount; }; } // namespace QInstaller diff --git a/src/libs/installer/downloadfiletask.cpp b/src/libs/installer/downloadfiletask.cpp index 1b9f81ecc..a959677a9 100644 --- a/src/libs/installer/downloadfiletask.cpp +++ b/src/libs/installer/downloadfiletask.cpp @@ -1,7 +1,7 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -30,6 +30,7 @@ #include "downloadfiletask_p.h" #include "globals.h" +#include "productkeycheck.h" #include <QCoreApplication> #include <QDir> @@ -197,7 +198,7 @@ void Downloader::onReadyRead() data.observer->addSample(read); data.observer->addBytesTransfered(read); - data.observer->addCheckSumData(buffer.data(), read); + data.observer->addCheckSumData(buffer.left(read)); int progress = m_finished * 100; for (const auto &pair : m_downloads) @@ -246,7 +247,7 @@ void Downloader::onFinished(QNetworkReply *reply) if (!ba.isEmpty()) { data.observer->addSample(ba.size()); data.observer->addBytesTransfered(ba.size()); - data.observer->addCheckSumData(ba.data(), ba.size()); + data.observer->addCheckSumData(ba); } const QByteArray expectedCheckSum = data.taskItem.value(TaskRole::Checksum).toByteArray(); @@ -269,7 +270,7 @@ void Downloader::onFinished(QNetworkReply *reply) } } -void Downloader::onError(QNetworkReply::NetworkError error) +void Downloader::errorOccurred(QNetworkReply::NetworkError error) { QNetworkReply *const reply = qobject_cast<QNetworkReply *>(sender()); @@ -286,6 +287,10 @@ void Downloader::onError(QNetworkReply::NetworkError error) if (data.taskItem.source().contains(QLatin1String("Updates.xml"), Qt::CaseInsensitive)) { qCWarning(QInstaller::lcServer) << QString::fromLatin1("Network error while downloading '%1': %2.").arg( data.taskItem.source(), reply->errorString()); + } else if (data.taskItem.source().contains(QLatin1String("_meta"), Qt::CaseInsensitive)) { + QString errorString = tr("Network error while downloading '%1': %2.").arg(data.taskItem.source(), reply->errorString()); + errorString.append(ProductKeyCheck::instance()->additionalMetaDownloadWarning()); + m_futureInterface->reportException(TaskException(errorString)); } else { m_futureInterface->reportException( TaskException(tr("Network error while downloading '%1': %2.").arg( @@ -395,14 +400,17 @@ QNetworkReply *Downloader::startDownload(const FileTaskItem &item) .arg(source.toString(), source.errorString()))); return 0; } + QNetworkRequest request(source); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); - QNetworkReply *reply = m_nam.get(QNetworkRequest(source)); + QNetworkReply *reply = m_nam.get(request); std::unique_ptr<Data> data(new Data(item)); m_downloads[reply] = std::move(data); connect(reply, &QIODevice::readyRead, this, &Downloader::onReadyRead); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, - SLOT(onError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, + SLOT(errorOccurred(QNetworkReply::NetworkError))); #ifndef QT_NO_SSL connect(reply, &QNetworkReply::sslErrors, this, &Downloader::onSslErrors); #endif diff --git a/src/libs/installer/downloadfiletask_p.h b/src/libs/installer/downloadfiletask_p.h index 3dfce27b4..4750d5134 100644 --- a/src/libs/installer/downloadfiletask_p.h +++ b/src/libs/installer/downloadfiletask_p.h @@ -86,7 +86,7 @@ private slots: void doDownload(); void onReadyRead(); void onFinished(QNetworkReply *reply); - void onError(QNetworkReply::NetworkError error); + void errorOccurred(QNetworkReply::NetworkError error); void onSslErrors(const QList<QSslError> &sslErrors); void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator); diff --git a/src/libs/installer/environmentvariablesoperation.cpp b/src/libs/installer/environmentvariablesoperation.cpp index 902687164..94cd1e36f 100644 --- a/src/libs/installer/environmentvariablesoperation.cpp +++ b/src/libs/installer/environmentvariablesoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -30,6 +30,7 @@ #include "qsettingswrapper.h" #include <stdlib.h> +#include <QDir> #include "environment.h" #include "globals.h" @@ -110,6 +111,12 @@ bool handleRegExpandSz(const QString ®Path, const QString &name, } } } +#else + Q_UNUSED(regPath) + Q_UNUSED(name) + Q_UNUSED(value) + Q_UNUSED(errorString) + Q_UNUSED(error) #endif return setAsExpandSZ; } @@ -162,11 +169,11 @@ UpdateOperation::Error undoSetting(const QString ®Path, if (actual != value) { - //For unknown reason paths with @TargetDir@ variable get modified - //so that Windows file separators get replaced with unix style separators, - //fix separators before matching to actual value in register + //Ignore the separators + static const QRegularExpression regex(QLatin1String("(\\\\|/)")); QString tempValue = value; - QString fixedValue = tempValue.replace(QLatin1Char('/'), QLatin1Char('\\')); + QString fixedValue = tempValue.replace(regex, QDir::separator()); + actual = actual.replace(regex, QDir::separator()); if (actual != fixedValue) //key changed, don't undo return UpdateOperation::UserDefinedError; diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index 162bd1609..b00a67190 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -103,7 +103,7 @@ void ExtractArchiveOperation::backup() return; } - const bool hasAdminRights = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()); + const bool hasAdminRights = (packageManager() && packageManager()->hasAdminRights()); const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks(); bool needsAdminRights = false; @@ -215,7 +215,8 @@ bool ExtractArchiveOperation::performOperation() } files[i] = replacePath(files.at(i), installDir, QLatin1String(scRelocatable)); } - out << files; + if (!files.isEmpty()) + out << files; setValue(QLatin1String("files"), file.fileName()); file.close(); } else { @@ -253,7 +254,8 @@ bool ExtractArchiveOperation::undoOperation() if (!readDataFileContents(targetDir, &files)) return false; } - startUndoProcess(files); + if (!files.isEmpty()) + startUndoProcess(files); if (!useStringListType) deleteDataFile(m_relocatedDataFileName); diff --git a/src/libs/installer/fakestopprocessforupdateoperation.cpp b/src/libs/installer/fakestopprocessforupdateoperation.cpp index 67d60a92f..bdd8625eb 100644 --- a/src/libs/installer/fakestopprocessforupdateoperation.cpp +++ b/src/libs/installer/fakestopprocessforupdateoperation.cpp @@ -31,6 +31,8 @@ #include "messageboxhandler.h" #include "packagemanagercore.h" +#include <QDir> + using namespace KDUpdater; using namespace QInstaller; @@ -79,11 +81,11 @@ bool FakeStopProcessForUpdateOperation::undoOperation() if (processes.count() == 1) { setError(UpdateOperation::UserDefinedError, tr("This process should be stopped before " - "continuing: %1").arg(processes.first())); + "continuing: %1").arg(QDir::toNativeSeparators(processes.first()))); } else { const QString sep = QString::fromWCharArray(L"\n \u2022 "); // Unicode bullet setError(UpdateOperation::UserDefinedError, tr("These processes should be stopped before " - "continuing: %1").arg(sep + processes.join(sep))); + "continuing: %1").arg(sep + QDir::toNativeSeparators(processes.join(sep)))); } return false; } diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp index 2147a8978..044eeb34f 100644 --- a/src/libs/installer/fileutils.cpp +++ b/src/libs/installer/fileutils.cpp @@ -734,7 +734,7 @@ quint64 QInstaller::fileSize(const QFileInfo &info) bool QInstaller::isInBundle(const QString &path, QString *bundlePath) { #ifdef Q_OS_MACOS - QFileInfo fi = QFileInfo(path).absoluteFilePath(); + QFileInfo fi(QFileInfo(path).absoluteFilePath()); while (!fi.isRoot()) { if (fi.isBundle()) { if (bundlePath) diff --git a/src/libs/installer/genericdatacache.cpp b/src/libs/installer/genericdatacache.cpp index fd264ce63..a1e31ccfe 100644 --- a/src/libs/installer/genericdatacache.cpp +++ b/src/libs/installer/genericdatacache.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -120,6 +120,16 @@ CacheableItem::~CacheableItem() */ /*! + \enum GenericDataCache::RegisterMode + This enum holds the possible values for modes of registering items to cache. + + \value Copy + The contents of the item are copied to the cache. + \value Move + The contents of the item are move to the cache. +*/ + +/*! \fn template <typename T> QInstaller::GenericDataCache<T>::GenericDataCache() Constructs a new empty cache. The cache is invalid until set with a @@ -411,19 +421,20 @@ T *GenericDataCache<T>::itemByPath(const QString &path) const } /*! - \fn template <typename T> QInstaller::GenericDataCache<T>::registerItem(T *item, bool replace) + \fn template <typename T> QInstaller::GenericDataCache<T>::registerItem(T *item, bool replace, RegisterMode mode) Registers the \a item to the cache. If \a replace is set to \c true, the new \a item replaces a previous item with the same checksum. The cache takes ownership of the object pointed by \a item. The contents of the - item are copied to the cache with a subdirectory name that matches the checksum - of the item. + item are copied or moved to the cache with a subdirectory name that matches the checksum + of the item. The \a mode decides how the contents of the item are registered, either by + copying or moving. Returns \c true on success or \c false if the item could not be registered. */ template <typename T> -bool GenericDataCache<T>::registerItem(T *item, bool replace) +bool GenericDataCache<T>::registerItem(T *item, bool replace, RegisterMode mode) { QMutexLocker _(&m_mutex); if (m_invalidated) { @@ -455,10 +466,27 @@ bool GenericDataCache<T>::registerItem(T *item, bool replace) const QString newPath = m_path + QDir::separator() + QString::fromLatin1(item->checksum()); try { // A directory is in the way but it isn't registered to the current cache, remove. - if (QDir().exists(newPath)) + QDir dir; + if (dir.exists(newPath)) QInstaller::removeDirectory(newPath); - QInstaller::copyDirectoryContents(item->path(), newPath); + switch (mode) { + case Copy: + QInstaller::copyDirectoryContents(item->path(), newPath); + break; + case Move: + // First, try moving the top level directory + if (!dir.rename(item->path(), newPath)) { + qCDebug(lcDeveloperBuild) << "Failed to rename directory" << item->path() + << "to" << newPath << ". Trying again."; + // If that does not work, fallback to moving the contents one by one + QInstaller::moveDirectoryContents(item->path(), newPath); + } + break; + default: + throw Error(QCoreApplication::translate("GenericDataCache", + "Unknown register mode selected!")); + } } catch (const Error &e) { setErrorString(QCoreApplication::translate("GenericDataCache", "Error while copying item to path \"%1\": %2").arg(newPath, e.message())); @@ -587,8 +615,8 @@ bool GenericDataCache<T>::fromDisk() for (const auto &itemJsonValue : itemsJsonArray) { const QString checksum = itemJsonValue.toString(); - QScopedPointer<T> item(new T(m_path + QDir::separator() + checksum)); - m_items.insert(checksum.toLatin1(), item.take()); + std::unique_ptr<T> item(new T(m_path + QDir::separator() + checksum)); + m_items.insert(checksum.toLatin1(), item.release()); // The cache directory may contain other entries (unrelated directories or // invalid old cache items) which we don't care about, unless registering diff --git a/src/libs/installer/genericdatacache.h b/src/libs/installer/genericdatacache.h index 2bdf6697e..94085502c 100644 --- a/src/libs/installer/genericdatacache.h +++ b/src/libs/installer/genericdatacache.h @@ -64,9 +64,14 @@ template <typename T> class INSTALLER_EXPORT GenericDataCache { public: + enum RegisterMode { + Copy = 0, + Move = 1 + }; + GenericDataCache(); explicit GenericDataCache(const QString &path, const QString &type, const QString &version); - ~GenericDataCache(); + virtual ~GenericDataCache(); void setType(const QString &type); void setVersion(const QString &version); @@ -85,7 +90,7 @@ public: T *itemByChecksum(const QByteArray &checksum) const; T *itemByPath(const QString &path) const; - bool registerItem(T *item, bool replace = false); + bool registerItem(T *item, bool replace = false, RegisterMode mode = Copy); bool removeItem(const QByteArray &checksum); QList<T *> obsoleteItems() const; diff --git a/src/libs/installer/globals.cpp b/src/libs/installer/globals.cpp index 5240138c4..3fd084768 100644 --- a/src/libs/installer/globals.cpp +++ b/src/libs/installer/globals.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -30,6 +30,14 @@ #include "globals.h" +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) +#include <termios.h> +#include <unistd.h> +#elif defined(Q_OS_WIN) +#include <conio.h> +#endif +#include <iostream> + const char IFW_SERVER[] = "ifw.server"; const char IFW_INSTALLER_INSTALLLOG[] = "ifw.installer.installlog"; const char IFW_DEVELOPER_BUILD[] = "ifw.developer.build"; @@ -78,7 +86,9 @@ QStringList loggingCategories() { static QStringList categories = QStringList() << QLatin1String(IFW_INSTALLER_INSTALLLOG) - << QLatin1String(IFW_SERVER); + << QLatin1String(IFW_SERVER) + << QLatin1String(IFW_DEVELOPER_BUILD) + << QLatin1String("js"); return categories; } @@ -117,5 +127,52 @@ QString enumToString(const QMetaObject& metaObject, const char *enumerator, int return value; } +void askForCredentials(QString *username, QString *password, const QString &usernameTitle, const QString &passwordTitle) +{ + std::string usernameStdStr; + std::string passwordStdStr; + + std::cout << qPrintable(usernameTitle); + std::cin >> usernameStdStr; + + std::cout << qPrintable(passwordTitle); +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) + termios oldTerm; + termios term; + + // Turn off echoing + tcgetattr(STDIN_FILENO, &oldTerm); + term = oldTerm; + term.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &term); + + std::cin >> passwordStdStr; + + // Clear input buffer + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + + // Restore old attributes + tcsetattr(STDIN_FILENO, TCSANOW, &oldTerm); +#elif defined(Q_OS_WIN) + char ch; + while ((ch = _getch()) != '\r') { // Return key + if (ch == '\b') { // Backspace key + if (!passwordStdStr.empty()) + passwordStdStr.pop_back(); + } else { + passwordStdStr.push_back(ch); + } + } + // Clear input buffer + int c; + while ((c = getchar()) != '\n' && c != EOF); + +#endif + std::cout << "\n"; + + *username = username->fromStdString(usernameStdStr); + *password = password->fromStdString(passwordStdStr); +} + } // namespace QInstaller diff --git a/src/libs/installer/globals.h b/src/libs/installer/globals.h index 3b3f4e3ab..2d119048b 100644 --- a/src/libs/installer/globals.h +++ b/src/libs/installer/globals.h @@ -59,6 +59,8 @@ QSet<T> toQSet(const C<T> &container) return QSet<T>(container.begin(), container.end()); } +void askForCredentials(QString *username, QString *password, const QString &usernameTitle, const QString &passwordTitle); + } // QInstaller #endif // GLOBALS_H diff --git a/src/libs/installer/globalsettingsoperation.cpp b/src/libs/installer/globalsettingsoperation.cpp index 9608bba66..6ca50f96f 100644 --- a/src/libs/installer/globalsettingsoperation.cpp +++ b/src/libs/installer/globalsettingsoperation.cpp @@ -77,7 +77,7 @@ bool GlobalSettingsOperation::performOperation() bool GlobalSettingsOperation::undoOperation() { - if (parseUndoOperationArguments().count() > 0) + if (skipUndoOperation()) return true; const QStringList args = parsePerformOperationArguments(); diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index 5a58968ee..ff7a0eed2 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -47,11 +47,13 @@ greaterThan(QT_MAJOR_VERSION, 5):QT += core5compat HEADERS += packagemanagercore.h \ aspectratiolabel.h \ calculatorbase.h \ + componentalias.h \ componentsortfilterproxymodel.h \ concurrentoperationrunner.h \ genericdatacache.h \ loggingutils.h \ metadata.h \ + metadatacache.h \ packagemanagercore_p.h \ packagemanagergui.h \ binaryformat.h \ @@ -144,13 +146,15 @@ HEADERS += packagemanagercore.h \ abstractarchive.h \ directoryguard.h \ archivefactory.h \ - operationtracer.h + operationtracer.h \ + customcombobox.h SOURCES += packagemanagercore.cpp \ abstractarchive.cpp \ archivefactory.cpp \ aspectratiolabel.cpp \ calculatorbase.cpp \ + componentalias.cpp \ concurrentoperationrunner.cpp \ directoryguard.cpp \ fileguard.cpp \ @@ -158,6 +162,7 @@ SOURCES += packagemanagercore.cpp \ genericdatacache.cpp \ loggingutils.cpp \ metadata.cpp \ + metadatacache.cpp \ operationtracer.cpp \ packagemanagercore_p.cpp \ packagemanagergui.cpp \ @@ -231,7 +236,8 @@ SOURCES += packagemanagercore.cpp \ packagesource.cpp \ repositorycategory.cpp \ componentselectionpage_p.cpp \ - commandlineparser.cpp + commandlineparser.cpp \ + customcombobox.cpp macos:SOURCES += fileutils_mac.mm diff --git a/src/libs/installer/installer_global.h b/src/libs/installer/installer_global.h index ea6865042..285eff910 100644 --- a/src/libs/installer/installer_global.h +++ b/src/libs/installer/installer_global.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -40,4 +40,10 @@ # define INSTALLER_EXPORT #endif +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +typedef uint hashValue; +#else +typedef size_t hashValue; +#endif + #endif //INSTALLER_GLOBAL_H diff --git a/src/libs/installer/installercalculator.cpp b/src/libs/installer/installercalculator.cpp index 105e99fd8..4c53824af 100644 --- a/src/libs/installer/installercalculator.cpp +++ b/src/libs/installer/installercalculator.cpp @@ -29,6 +29,8 @@ #include "installercalculator.h" #include "component.h" +#include "componentalias.h" +#include "componentmodel.h" #include "packagemanagercore.h" #include "settings.h" #include <globals.h> @@ -51,6 +53,19 @@ InstallerCalculator::~InstallerCalculator() { } +bool InstallerCalculator::solve() +{ + if (!solve(m_core->aliasesMarkedForInstallation())) + return false; + + // Subtract components added by aliases + QList<Component *> components = m_core->componentsMarkedForInstallation(); + for (auto *component : qAsConst(m_resolvedComponents)) + components.removeAll(component); + + return solve(components); +} + QString InstallerCalculator::resolutionText(Component *component) const { const Resolution reason = resolutionType(component); @@ -67,6 +82,9 @@ QString InstallerCalculator::resolutionText(Component *component) const case Resolution::Selected: return QCoreApplication::translate("InstallerCalculator", "Selected components without dependencies:"); + case Resolution::Alias: + return QCoreApplication::translate("InstallerCalculator", + "Components selected by alias \"%1\":").arg(referencedComponent(component)); default: Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid install resolution detected!"); } @@ -110,6 +128,44 @@ bool InstallerCalculator::solve(const QList<Component *> &components) return true; } +bool InstallerCalculator::solve(const QList<ComponentAlias *> &aliases) +{ + if (aliases.isEmpty()) + return true; + + QList<ComponentAlias *> notAppendedAliases; // Aliases that require other aliases + for (auto *alias : aliases) { + if (!alias) + continue; + + if (m_toInstallComponentAliases.contains(alias->name())) { + const QString errorMessage = QCoreApplication::translate("InstallerCalculator", + "Recursion detected, component alias \"%1\" already added.").arg(alias->name()); + qCWarning(QInstaller::lcInstallerInstallLog).noquote() << errorMessage; + m_errorString.append(errorMessage); + + Q_ASSERT_X(!m_toInstallComponentAliases.contains(alias->name()), Q_FUNC_INFO, + qPrintable(errorMessage)); + + return false; + } + + if (alias->aliases().isEmpty()) { + if (!addComponentsFromAlias(alias)) + return false; + } else { + notAppendedAliases.append(alias); + } + } + + for (auto *alias : qAsConst(notAppendedAliases)) { + if (!solveAlias(alias)) + return false; + } + + return true; +} + void InstallerCalculator::addComponentForInstall(Component *component, const QString &version) { if (!m_componentsForAutodepencencyCheck.contains(component)) @@ -121,12 +177,40 @@ void InstallerCalculator::addComponentForInstall(Component *component, const QSt } } +bool InstallerCalculator::addComponentsFromAlias(ComponentAlias *alias) +{ + QList<Component *> componentsToAdd; + for (auto *component : alias->components()) { + if (m_toInstallComponentIds.contains(component->name())) + continue; // Already added + + componentsToAdd.append(component); + // Updates the model, so that we also check the descendant + // components when calculating components to install + updateCheckState(component, Qt::Checked); + insertResolution(component, Resolution::Alias, alias->name()); + } + + m_toInstallComponentAliases.insert(alias->name()); + return solve(componentsToAdd); +} + QString InstallerCalculator::recursionError(Component *component) const { return QCoreApplication::translate("InstallerCalculator", "Recursion detected, component \"%1\" " "already added with reason: \"%2\"").arg(component->name(), resolutionText(component)); } +bool InstallerCalculator::updateCheckState(Component *component, Qt::CheckState state) +{ + ComponentModel *currentModel = m_core->isUpdater() + ? m_core->updaterComponentModel() + : m_core->defaultComponentModel(); + + const QModelIndex &idx = currentModel->indexFromComponentName(component->treeName()); + return currentModel->setData(idx, state, Qt::CheckStateRole); +} + bool InstallerCalculator::solveComponent(Component *component, const QString &version) { const QStringList dependenciesList = component->currentDependencies(); @@ -200,6 +284,19 @@ bool InstallerCalculator::solveComponent(Component *component, const QString &ve return true; } +bool InstallerCalculator::solveAlias(ComponentAlias *alias) +{ + for (auto *requiredAlias : alias->aliases()) { + if (!solveAlias(requiredAlias)) + return false; + } + + if (m_toInstallComponentAliases.contains(alias->name())) + return true; + + return addComponentsFromAlias(alias); +} + QSet<Component *> InstallerCalculator::autodependencyComponents() { // All regular dependencies are resolved. Now we are looking for auto depend on components. diff --git a/src/libs/installer/installercalculator.h b/src/libs/installer/installercalculator.h index 339dbeffd..e542dc664 100644 --- a/src/libs/installer/installercalculator.h +++ b/src/libs/installer/installercalculator.h @@ -40,6 +40,7 @@ namespace QInstaller { class Component; +class ComponentAlias; class PackageManagerCore; class INSTALLER_EXPORT InstallerCalculator : public CalculatorBase @@ -48,20 +49,28 @@ public: InstallerCalculator(PackageManagerCore *core, const AutoDependencyHash &autoDependencyComponentHash); ~InstallerCalculator(); + bool solve(); bool solve(const QList<Component *> &components) override; + bool solve(const QList<ComponentAlias *> &aliases); + QString resolutionText(Component *component) const override; private: bool solveComponent(Component *component, const QString &version = QString()) override; + bool solveAlias(ComponentAlias *alias); void addComponentForInstall(Component *component, const QString &version = QString()); + bool addComponentsFromAlias(ComponentAlias *alias); QSet<Component *> autodependencyComponents(); QString recursionError(Component *component) const; + bool updateCheckState(Component *component, Qt::CheckState state); + private: QHash<Component*, QSet<Component*> > m_visitedComponents; QList<const Component*> m_componentsForAutodepencencyCheck; QSet<QString> m_toInstallComponentIds; //for faster lookups + QSet<QString> m_toInstallComponentAliases; //Helper hash for quicker search for autodependency components AutoDependencyHash m_autoDependencyComponentHash; }; diff --git a/src/libs/installer/installiconsoperation.cpp b/src/libs/installer/installiconsoperation.cpp index 5927ebf74..b21634cd7 100644 --- a/src/libs/installer/installiconsoperation.cpp +++ b/src/libs/installer/installiconsoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -57,7 +57,7 @@ QString InstallIconsOperation::targetDirectory() Qt::SkipEmptyParts); XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default user-specific path - if (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()) + if (packageManager() && packageManager()->hasAdminRights()) XDG_DATA_HOME.push_front(QLatin1String("/usr/local/share")); // default system-wide path QString directory; diff --git a/src/libs/installer/libarchivewrapper_p.cpp b/src/libs/installer/libarchivewrapper_p.cpp index e5c1e8598..b4325243d 100644 --- a/src/libs/installer/libarchivewrapper_p.cpp +++ b/src/libs/installer/libarchivewrapper_p.cpp @@ -41,12 +41,14 @@ namespace QInstaller { */ /*! + \internal \fn QInstaller::LibArchiveWrapperPrivate::dataBlockRequested() Emitted when the server process has requested another data block. */ /*! + \internal \fn QInstaller::LibArchiveWrapperPrivate::remoteWorkerFinished() Emitted when the server process has finished extracting an archive. @@ -105,7 +107,7 @@ void LibArchiveWrapperPrivate::setFilename(const QString &filename) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilename), filename, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveSetFilename), filename); m_lock.unlock(); } m_archive.setFilename(filename); @@ -145,7 +147,7 @@ bool LibArchiveWrapperPrivate::extract(const QString &dirPath, const quint64 tot timer.start(); m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveExtract), dirPath, total); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveExtract), dirPath, total); m_lock.unlock(); { QEventLoop loop; @@ -203,7 +205,7 @@ void LibArchiveWrapperPrivate::setCompressionLevel(const AbstractArchive::Compre { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetCompressionLevel), level, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveSetCompressionLevel), level); m_lock.unlock(); return; } @@ -219,7 +221,7 @@ void LibArchiveWrapperPrivate::cancel() { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveCancel)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveCancel)); m_lock.unlock(); return; } @@ -354,7 +356,7 @@ void LibArchiveWrapperPrivate::addDataBlock(const QByteArray &buffer) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveAddDataBlock), buffer, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveAddDataBlock), buffer); m_lock.unlock(); } } @@ -367,7 +369,7 @@ void LibArchiveWrapperPrivate::setClientDataAtEnd() { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)); m_lock.unlock(); } } @@ -379,7 +381,7 @@ void LibArchiveWrapperPrivate::setClientFilePosition(qint64 pos) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilePosition), pos, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::AbstractArchiveSetFilePosition), pos); m_lock.unlock(); } } diff --git a/src/libs/installer/licenseoperation.cpp b/src/libs/installer/licenseoperation.cpp index 6cf0c8e3e..0d30ab514 100644 --- a/src/libs/installer/licenseoperation.cpp +++ b/src/libs/installer/licenseoperation.cpp @@ -31,6 +31,7 @@ #include "packagemanagercore.h" #include "settings.h" #include "fileutils.h" +#include "globals.h" #include <QtCore/QDir> #include <QtCore/QFile> @@ -56,7 +57,7 @@ void LicenseOperation::backup() bool LicenseOperation::performOperation() { - QVariantMap licenses = value(scLicenses).toMap(); + QVariantMap licenses = value(scLicensesValue).toMap(); if (licenses.isEmpty()) { setError(UserDefinedError); setErrorString(tr("No license files found to copy.")); @@ -86,9 +87,11 @@ bool LicenseOperation::performOperation() return false; } - QTextStream stream(&file); - stream.setCodec("UTF-8"); + QString outString; + QTextStream stream(&outString); stream << it.value().toString(); + + file.write(outString.toUtf8()); } return true; @@ -96,11 +99,10 @@ bool LicenseOperation::performOperation() bool LicenseOperation::undoOperation() { - const QVariantMap licenses = value(scLicenses).toMap(); + const QVariantMap licenses = value(scLicensesValue).toMap(); if (licenses.isEmpty()) { - setError(UserDefinedError); - setErrorString(tr("No license files found to delete.")); - return false; + qCWarning(QInstaller::lcInstallerInstallLog) << "No license files found to delete."; + return true; } QString targetDir = arguments().value(0); diff --git a/src/libs/installer/loggingutils.cpp b/src/libs/installer/loggingutils.cpp index 42bbc0117..9a36720dd 100644 --- a/src/libs/installer/loggingutils.cpp +++ b/src/libs/installer/loggingutils.cpp @@ -29,6 +29,7 @@ #include "loggingutils.h" #include "component.h" +#include "componentalias.h" #include "globals.h" #include "fileutils.h" #include "packagemanagercore.h" @@ -367,6 +368,44 @@ void LoggingHandler::printPackageInformation(const PackagesList &matchedPackages } /*! + Prints basic or more detailed information about component \a aliases, + depending on the current verbosity level. +*/ +void LoggingHandler::printAliasInformation(const QList<ComponentAlias *> &aliases) +{ + QList<ComponentAlias *> sortedAliases = aliases; + std::sort(sortedAliases.begin(), sortedAliases.end(), + [](const ComponentAlias *lhs, const ComponentAlias *rhs) { + return lhs->name() < rhs->name(); + } + ); + + QString output; + QTextStream stream(&output); + + stream << Qt::endl; + for (auto *alias : qAsConst(sortedAliases)) { + stream << "Name: " << alias->name() << Qt::endl; + stream << "Display name: " << alias->displayName() << Qt::endl; + stream << "Description: " << alias->description() << Qt::endl; + stream << "Version: " << alias->version() << Qt::endl; + if (verboseLevel() == VerbosityLevel::Detailed) + stream << "Virtual: " << alias->value(scVirtual) << Qt::endl; + + stream << "Components: " << alias->value(scRequiredComponents) << Qt::endl; + stream << "Required aliases: " << alias->value(scRequiredAliases) << Qt::endl; + + stream << "Optional components: " << alias->value(scOptionalComponents) << Qt::endl; + stream << "Optional aliases: " << alias->value(scOptionalAliases) << Qt::endl; + + if (sortedAliases.indexOf(alias) != (sortedAliases.count() - 1)) + stream << "========================================" << Qt::endl; + } + + std::cout << qPrintable(output); +} + +/*! \internal */ VerboseWriter::VerboseWriter() diff --git a/src/libs/installer/loggingutils.h b/src/libs/installer/loggingutils.h index 8bd4217ee..18ff2d2c5 100644 --- a/src/libs/installer/loggingutils.h +++ b/src/libs/installer/loggingutils.h @@ -41,6 +41,7 @@ namespace QInstaller { class Component; +class ComponentAlias; class INSTALLER_EXPORT LoggingHandler { @@ -67,6 +68,7 @@ public: void printUpdateInformation(const QList<Component *> &components) const; void printLocalPackageInformation(const QList<KDUpdater::LocalPackage> &packages) const; void printPackageInformation(const PackagesList &matchedPackages, const LocalPackagesMap &installedPackages) const; + void printAliasInformation(const QList<ComponentAlias *> &aliases); friend VerbosityLevel &operator++(VerbosityLevel &level, int); friend VerbosityLevel &operator--(VerbosityLevel &level, int); diff --git a/src/libs/installer/messageboxhandler.cpp b/src/libs/installer/messageboxhandler.cpp index 78abc88fa..052709e51 100644 --- a/src/libs/installer/messageboxhandler.cpp +++ b/src/libs/installer/messageboxhandler.cpp @@ -368,7 +368,9 @@ static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, QMessageBo QMessageBox::StandardButton defaultButton) { QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent); - msgBox.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + msgBox.setTextInteractionFlags(Qt::TextBrowserInteraction); + msgBox.setTextFormat(Qt::RichText); + QDialogButtonBox *buttonBox = msgBox.findChild<QDialogButtonBox *>(); Q_ASSERT(buttonBox != nullptr); diff --git a/src/libs/installer/metadata.cpp b/src/libs/installer/metadata.cpp index 9ae817127..2eccb020e 100644 --- a/src/libs/installer/metadata.cpp +++ b/src/libs/installer/metadata.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -46,6 +46,52 @@ namespace QInstaller { \brief The Metadata class represents fetched metadata from a repository. */ + +/*! + \internal +*/ +static bool verifyFileIntegrityFromElement(const QDomElement &element, const QString &childNodeName, + const QString &attribute, const QString &metaDirectory, bool testChecksum) +{ + const QDomNodeList nodes = element.childNodes(); + for (int i = 0; i < nodes.count(); ++i) { + const QDomNode node = nodes.at(i); + if (node.nodeName() != childNodeName) + continue; + + const QDir dir(metaDirectory); + const QString filename = attribute.isEmpty() + ? node.toElement().text() + : node.toElement().attribute(attribute); + + if (filename.isEmpty()) + continue; + + QFile file(dir.absolutePath() + QDir::separator() + filename); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot open" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + + if (!testChecksum) + continue; + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + const QByteArray checksum = hash.result().toHex(); + if (!QFileInfo::exists(dir.absolutePath() + QDir::separator() + + QString::fromLatin1(checksum) + QLatin1String(".sha1"))) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Unexpected checksum for file" << file.fileName(); + return false; + } + } + return true; +} + /*! Constructs a new metadata object. */ @@ -122,18 +168,23 @@ QDomDocument Metadata::updatesDocument() const } /*! - Returns \c true if the \c Updates.xml document of this metadata - exists, \c false otherwise. + Returns \c true if the \c Updates.xml document of this metadata exists, and that all + meta files referenced in the document exist. If the \c Updates.xml contains a \c Checksum + element with a value of \c true, the integrity of the files is also verified. + + Returns \c false otherwise. */ bool Metadata::isValid() const { - const QString updateFile(path() + QLatin1String("/Updates.xml")); - if (!QFileInfo::exists(updateFile)) { + QFile updateFile(path() + QLatin1String("/Updates.xml")); + if (!updateFile.open(QIODevice::ReadOnly)) { qCWarning(QInstaller::lcInstallerInstallLog) - << "File" << updateFile << "does not exist."; + << "Cannot open" << updateFile.fileName() + << "for reading:" << updateFile.errorString(); return false; } - return true; + + return verifyMetaFiles(&updateFile); } /*! @@ -282,4 +333,78 @@ bool Metadata::containsRepositoryUpdates() const return false; } +/*! + Verifies that the files referenced in \a updateFile document exist + on disk. If the document contains a \c Checksum element with a value + of \c true, the integrity of the files is also verified. + + Returns \c true if the meta files are valid, \c false otherwise. +*/ +bool Metadata::verifyMetaFiles(QFile *updateFile) const +{ + QDomDocument doc; + QString errorString; + if (!doc.setContent(updateFile, &errorString)) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "Cannot set document content:" << errorString; + return false; + } + + const QDomElement rootElement = doc.documentElement(); + const QDomNodeList childNodes = rootElement.childNodes(); + + bool testChecksum = true; + const QDomElement checksumElement = rootElement.firstChildElement(QLatin1String("Checksum")); + if (!checksumElement.isNull()) + testChecksum = (checksumElement.text().toLower() == scTrue); + + for (int i = 0; i < childNodes.count(); ++i) { + const QDomElement element = childNodes.at(i).toElement(); + if (element.isNull() || element.tagName() != QLatin1String("PackageUpdate")) + continue; + + const QDomNodeList c2 = element.childNodes(); + QString packageName; + QString unused1; + QString unused2; + + // Only need the package name, so values for "online" and "testCheckSum" do not matter + if (!MetadataJob::parsePackageUpdate(c2, packageName, unused1, unused2, true, true)) + continue; // nothing to check for this package + + const QString packagePath = QString::fromLatin1("%1/%2/").arg(path(), packageName); + for (auto &metaTagName : scMetaElements) { + const QDomElement metaElement = element.firstChildElement(metaTagName); + if (metaElement.isNull()) + continue; + + if (metaElement.tagName() == QLatin1String("Licenses")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("License"), + QLatin1String("file"), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("UserInterfaces")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("UserInterface"), + QString(), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("Translations")) { + if (!verifyFileIntegrityFromElement(metaElement, QLatin1String("Translation"), + QString(), packagePath, testChecksum)) { + return false; + } + } else if (metaElement.tagName() == QLatin1String("Script")) { + if (!verifyFileIntegrityFromElement(metaElement.parentNode().toElement(), + QLatin1String("Script"), QString(), packagePath, testChecksum)) { + return false; + } + } else { + Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown meta element."); + } + } + } + + return true; +} + } // namespace QInstaller diff --git a/src/libs/installer/metadata.h b/src/libs/installer/metadata.h index 3063be829..c7e4e857c 100644 --- a/src/libs/installer/metadata.h +++ b/src/libs/installer/metadata.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -35,6 +35,8 @@ #include <QDomDocument> +class QFile; + namespace QInstaller { class INSTALLER_EXPORT Metadata : public CacheableItem @@ -64,6 +66,9 @@ public: bool containsRepositoryUpdates() const; private: + bool verifyMetaFiles(QFile *updateFile) const; + +private: Repository m_repository; QString m_persistentRepositoryPath; mutable QByteArray m_checksum; @@ -71,13 +76,6 @@ private: bool m_fromDefaultRepository; }; -Q_GLOBAL_STATIC_WITH_ARGS(QStringList, scMetaElements, (QStringList( - QLatin1String("Script")) << - QLatin1String("Licenses") << - QLatin1String("UserInterfaces") << - QLatin1String("Translations") -)); - } // namespace QInstaller #endif // METADATA_H diff --git a/src/libs/installer/metadatacache.cpp b/src/libs/installer/metadatacache.cpp new file mode 100644 index 000000000..744e455f4 --- /dev/null +++ b/src/libs/installer/metadatacache.cpp @@ -0,0 +1,67 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "metadatacache.h" + +#define QUOTE_(x) #x +#define QUOTE(x) QUOTE_(x) + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::MetadataCache + \brief The MetadataCache is a class for a checksum based storage of \c Metadata objects on disk. + + MetadataCache manages a cache storage for a set \l{path()}, which contains + a subdirectory for each registered \c Metadata item. The cache has a manifest file in + its root directory, which lists the version and type of the cache, and all its items. + The file is updated automatically when the metadata cache object is destructed, or + it can be updated periodically by calling \l{sync()}. +*/ + +/*! + Constructs a new empty cache. The cache is invalid until set with a + path and initialized. +*/ +MetadataCache::MetadataCache() + : GenericDataCache<Metadata>() +{ + setType(QLatin1String("Metadata")); + setVersion(QLatin1String(QUOTE(IFW_CACHE_FORMAT_VERSION))); +} + +/*! + Constructs a cache to \a path. The cache is initialized automatically. +*/ +MetadataCache::MetadataCache(const QString &path) + : GenericDataCache(path, QLatin1String("Metadata"), QLatin1String(QUOTE(IFW_CACHE_FORMAT_VERSION))) +{ +} + +} // namespace QInstaller diff --git a/src/libs/installer/metadatacache.h b/src/libs/installer/metadatacache.h new file mode 100644 index 000000000..804d1b6db --- /dev/null +++ b/src/libs/installer/metadatacache.h @@ -0,0 +1,46 @@ +/************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef METADATACACHE_H +#define METADATACACHE_H + +#include "genericdatacache.h" +#include "metadata.h" + +namespace QInstaller { + +class MetadataCache : public GenericDataCache<Metadata> +{ +public: + MetadataCache(); + explicit MetadataCache(const QString &path); +}; + +} // namespace QInstaller + +#endif // METADATACACHE_H diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp index ff15d7b0d..1bed304c6 100644 --- a/src/libs/installer/metadatajob.cpp +++ b/src/libs/installer/metadatajob.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -41,9 +41,7 @@ #include <QtConcurrent> #include <QtMath> #include <QRandomGenerator> - -#define QUOTE_(x) #x -#define QUOTE(x) QUOTE_(x) +#include <QApplication> namespace QInstaller { @@ -52,7 +50,6 @@ namespace QInstaller { \value All \value CompressedPackage - \value UpdatesXML */ /*! @@ -145,9 +142,6 @@ QList<Metadata *> MetadataJob::metadata() const QHash<RepositoryCategory, QSet<Repository>>::const_iterator it; for (it = repositoryHash.constBegin(); it != repositoryHash.constEnd(); ++it) { - if (m_core->isUpdater()) - return true; - if (!it.key().isEnabled()) continue; // Let's try the next one @@ -192,8 +186,6 @@ bool MetadataJob::resetCache(bool init) m_metaFromCache.clear(); m_metaFromCache.setPath(m_core->settings().localCachePath()); - m_metaFromCache.setType(QLatin1String("Metadata")); - m_metaFromCache.setVersion(QLatin1String(QUOTE(IFW_REPOSITORY_FORMAT_VERSION))); if (!init) return true; @@ -218,6 +210,11 @@ bool MetadataJob::clearCache() return false; } +bool MetadataJob::isValidCache() const +{ + return m_metaFromCache.isValid(); +} + // -- private slots void MetadataJob::doStart() @@ -240,13 +237,15 @@ void MetadataJob::doStart() if (m_downloadType != DownloadType::CompressedPackage) { emit infoMessage(this, tr("Fetching latest update information...")); const bool onlineInstaller = m_core->isInstaller() && !m_core->isOfflineOnly(); - if (onlineInstaller || m_core->isMaintainer()) { + const QSet<Repository> repositories = getRepositories(); + + if (onlineInstaller || m_core->isMaintainer() + || (m_core->settings().allowRepositoriesForOfflineInstaller() && !repositories.isEmpty())) { static const QString updateFilePath(QLatin1Char('/') + scUpdatesXML + QLatin1Char('?')); static const QString randomQueryString = QString::number(QRandomGenerator::global()->generate()); - QList<FileTaskItem> items; - QSet<Repository> repositories = getRepositories(); quint64 cachedCount = 0; + setProgressTotalAmount(0); // Show only busy indicator during this loop as we have no progress to measure foreach (const Repository &repo, repositories) { // For not blocking the UI qApp->processEvents(); @@ -289,17 +288,24 @@ void MetadataJob::doStart() FileTaskItem item(url, tmp.path() + QLatin1String("/Updates.xml")); item.insert(TaskRole::UserRole, QVariant::fromValue(repo)); item.insert(TaskRole::Authenticator, QVariant::fromValue(authenticator)); - items.append(item); + m_updatesXmlItems.append(item); } } + setProgressTotalAmount(100); const quint64 totalCount = repositories.count(); if (cachedCount > 0) { qCDebug(lcInstallerInstallLog).nospace() << "Loaded from cache " << cachedCount << "/" << totalCount << ". Downloading remaining " - << items.count() << "/" << totalCount <<"."; + << m_updatesXmlItems.count() << "/" << totalCount <<"."; + } else { + qCDebug(lcInstallerInstallLog).nospace() <<"Downloading " << m_updatesXmlItems.count() + << " items to cache."; } - if (items.count() > 0) { - startXMLTask(items); + if (m_updatesXmlItems.count() > 0) { + double taskCount = m_updatesXmlItems.length()/static_cast<double>(m_downloadableChunkSize); + m_totalTaskCount = qCeil(taskCount); + m_taskNumber = 0; + startXMLTask(); } else { emitFinished(); } @@ -338,13 +344,22 @@ void MetadataJob::doStart() } } -void MetadataJob::startXMLTask(const QList<FileTaskItem> &items) +bool MetadataJob::startXMLTask() { - DownloadFileTask *const xmlTask = new DownloadFileTask(items); - xmlTask->setProxyFactory(m_core->proxyFactory()); - connect(&m_xmlTask, &QFutureWatcher<FileTaskResult>::progressValueChanged, this, - &MetadataJob::progressChanged); - m_xmlTask.setFuture(QtConcurrent::run(&DownloadFileTask::doTask, xmlTask)); + int chunkSize = qMin(m_updatesXmlItems.length(), m_downloadableChunkSize); + QList<FileTaskItem> tempPackages = m_updatesXmlItems.mid(0, chunkSize); + m_updatesXmlItems = m_updatesXmlItems.mid(chunkSize, m_updatesXmlItems.length()); + if (tempPackages.length() > 0) { + DownloadFileTask *const xmlTask = new DownloadFileTask(tempPackages); + xmlTask->setProxyFactory(m_core->proxyFactory()); + connect(&m_xmlTask, &QFutureWatcher<FileTaskResult>::progressValueChanged, this, + &MetadataJob::progressChanged); + m_xmlTask.setFuture(QtConcurrent::run(&DownloadFileTask::doTask, xmlTask)); + + setInfoMessage(tr("Retrieving information from remote repositories...")); + return true; + } + return false; } void MetadataJob::doCancel() @@ -431,7 +446,7 @@ void MetadataJob::unzipRepositoryTaskFinished() FileTaskItem item(url, tmp.path() + QLatin1String("/Updates.xml")); item.insert(TaskRole::UserRole, QVariant::fromValue(repo)); - m_unzipRepositoryitems.append(item); + m_updatesXmlItems.append(item); } else { //Repository is not valid, remove it Settings &s = m_core->settings(); @@ -451,8 +466,8 @@ void MetadataJob::unzipRepositoryTaskFinished() //One can specify many zipped repository items at once. As the repositories are //unzipped one by one, we collect here all items before parsing xml files from those. - if (m_unzipRepositoryitems.count() > 0 && m_unzipRepositoryTasks.isEmpty()) { - startXMLTask(m_unzipRepositoryitems); + if (m_updatesXmlItems.count() > 0 && m_unzipRepositoryTasks.isEmpty()) { + startXMLTask(); } else { if (error != Job::NoError) { emitFinishedWithError(QInstaller::DownloadError, errorString); @@ -476,19 +491,34 @@ void MetadataJob::xmlTaskFinished() Status status = XmlDownloadFailure; try { m_xmlTask.waitForFinished(); - status = parseUpdatesXml(m_xmlTask.future().results()); + m_updatesXmlResult.append(m_xmlTask.future().results()); + if (!startXMLTask()) { + status = parseUpdatesXml(m_updatesXmlResult); + m_updatesXmlResult.clear(); + } else { + return; + } } catch (const AuthenticationRequiredException &e) { if (e.type() == AuthenticationRequiredException::Type::Proxy) { - const QNetworkProxy proxy = e.proxy(); - ProxyCredentialsDialog proxyCredentials(proxy); qCWarning(QInstaller::lcInstallerInstallLog) << e.message(); - - if (proxyCredentials.exec() == QDialog::Accepted) { + QString username; + QString password; + const QNetworkProxy proxy = e.proxy(); + if (m_core->isCommandLineInstance()) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("The proxy %1:%2 requires a username and password").arg(proxy.hostName(), proxy.port()); + askForCredentials(&username, &password, QLatin1String("Username: "), QLatin1String("Password: ")); + } else { + ProxyCredentialsDialog proxyCredentials(proxy); + if (proxyCredentials.exec() == QDialog::Accepted) { + username = proxyCredentials.userName(); + password = proxyCredentials.password(); + } + } + if (!username.isEmpty()) { qCDebug(QInstaller::lcInstallerInstallLog) << "Retrying with new credentials ..."; PackageManagerProxyFactory *factory = m_core->proxyFactory(); - factory->setProxyCredentials(proxy, proxyCredentials.userName(), - proxyCredentials.password()); + factory->setProxyCredentials(proxy, username, password); m_core->setProxyFactory(factory); status = XmlDownloadRetry; } else { @@ -497,13 +527,25 @@ void MetadataJob::xmlTaskFinished() } } else if (e.type() == AuthenticationRequiredException::Type::Server) { qCWarning(QInstaller::lcInstallerInstallLog) << e.message(); - ServerAuthenticationDialog dlg(e.message(), e.taskItem()); - if (dlg.exec() == QDialog::Accepted) { + QString username; + QString password; + if (m_core->isCommandLineInstance()) { + qCDebug(QInstaller::lcInstallerInstallLog) << "Server Requires Authentication"; + qCDebug(QInstaller::lcInstallerInstallLog) << "You need to supply a username and password to access this site."; + askForCredentials(&username, &password, QLatin1String("Username: "), QLatin1String("Password: ")); + } else { + ServerAuthenticationDialog dlg(e.message(), e.taskItem()); + if (dlg.exec() == QDialog::Accepted) { + username = dlg.user(); + password = dlg.password(); + } + } + if (!username.isEmpty()) { Repository original = e.taskItem().value(TaskRole::UserRole) .value<Repository>(); Repository replacement = original; - replacement.setUsername(dlg.user()); - replacement.setPassword(dlg.password()); + replacement.setUsername(username); + replacement.setPassword(password); Settings &s = m_core->settings(); QSet<Repository> temporaries = s.temporaryRepositories(); @@ -557,8 +599,9 @@ void MetadataJob::xmlTaskFinished() // No new metadata packages to fetch, still need to update the cache // for refreshed repositories. startUpdateCacheTask(); - } + } } else if (status == XmlDownloadRetry) { + reset(); QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); } else { reset(); @@ -630,6 +673,7 @@ void MetadataJob::metadataTaskFinished() UnzipArchiveTask *task = new UnzipArchiveTask(result.target(), item.value(TaskRole::UserRole).toString()); task->setRemoveArchive(true); + task->setStoreChecksums(true); QFutureWatcher<void> *watcher = new QFutureWatcher<void>(); m_unzipTasks.insert(watcher, qobject_cast<QObject*> (task)); @@ -684,17 +728,11 @@ bool MetadataJob::fetchMetaDataPackages() QList<FileTaskItem> tempPackages = m_packages.mid(0, chunkSize); m_packages = m_packages.mid(chunkSize, m_packages.length()); if (tempPackages.length() > 0) { - m_taskNumber++; setProcessedAmount(0); DownloadFileTask *const metadataTask = new DownloadFileTask(tempPackages); metadataTask->setProxyFactory(m_core->proxyFactory()); m_metadataTask.setFuture(QtConcurrent::run(&DownloadFileTask::doTask, metadataTask)); - QString metaInformation; - if (m_totalTaskCount > 1) - metaInformation = tr("Retrieving meta information from remote repository... %1/%2 ").arg(m_taskNumber).arg(m_totalTaskCount); - else - metaInformation = tr("Retrieving meta information from remote repository... "); - emit infoMessage(this, metaInformation); + setInfoMessage(tr("Retrieving meta information from remote repository...")); return true; } return false; @@ -703,6 +741,7 @@ bool MetadataJob::fetchMetaDataPackages() void MetadataJob::reset() { m_packages.clear(); + m_updatesXmlItems.clear(); m_defaultRepositoriesFetched = false; m_fetchedCategorizedRepositories.clear(); @@ -721,6 +760,7 @@ void MetadataJob::reset() } catch (...) {} m_tempDirDeleter.releaseAndDeleteAll(); m_metadataResult.clear(); + m_updatesXmlResult.clear(); m_taskNumber = 0; } @@ -728,7 +768,6 @@ void MetadataJob::resetCompressedFetch() { setError(Job::NoError); setErrorString(QString()); - m_unzipRepositoryitems.clear(); try { foreach (QFutureWatcher<void> *const watcher, m_unzipTasks.keys()) { @@ -762,7 +801,7 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re } QFileInfo fileInfo(result.target()); - QScopedPointer<Metadata> metadata(new Metadata(fileInfo.absolutePath())); + std::unique_ptr<Metadata> metadata(new Metadata(fileInfo.absolutePath())); QFile file(result.target()); if (!file.open(QIODevice::ReadOnly)) { qCWarning(QInstaller::lcInstallerInstallLog) << "Cannot open Updates.xml for reading:" @@ -770,13 +809,21 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re return XmlDownloadFailure; } const FileTaskItem item = result.value(TaskRole::TaskItem).value<FileTaskItem>(); + const Repository repository = item.value(TaskRole::UserRole).value<Repository>(); - // Check if we have cached the metadata for this repository already QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(&file); const QByteArray updatesChecksum = hash.result().toHex(); + if (!repository.xmlChecksum().isEmpty() && updatesChecksum != repository.xmlChecksum()) { + qCWarning(lcDeveloperBuild).noquote().nospace() << "The checksum for Updates.xml " + "file downloaded from repository:\n" << repository.url().toString() << "\ndoes not " + "match the expected value:\n\tActual SHA1: " << updatesChecksum << "\n\tExpected SHA1: " + << repository.xmlChecksum() << Qt::endl; + } + bool refreshed; + // Check if we have cached the metadata for this repository already Status status = refreshCacheItem(result, updatesChecksum, &refreshed); if (status != XmlDownloadSuccess) return status; @@ -798,7 +845,7 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re } file.close(); - metadata->setRepository(item.value(TaskRole::UserRole).value<Repository>()); + metadata->setRepository(repository); const bool online = !(metadata->repository().url().scheme()).isEmpty(); bool testCheckSum = true; @@ -855,7 +902,7 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re m_fetchedCategorizedRepositories.insert(metadataPtr->repository()); // For faster lookups const QString metadataPath = metadata->path(); - m_fetchedMetadata.insert(metadataPath, metadata.take()); + m_fetchedMetadata.insert(metadataPath, metadata.release()); // search for additional repositories that we might need to check status = parseRepositoryUpdates(root, result, metadataPtr); @@ -885,6 +932,7 @@ MetadataJob::Status MetadataJob::refreshCacheItem(const FileTaskResult &result, const FileTaskItem item = result.value(TaskRole::TaskItem).value<FileTaskItem>(); const Repository repository = item.value(TaskRole::UserRole).value<Repository>(); + if (cachedMetadata->isValid() && !repository.isCompressed()) { // Refresh repository information to cache. Same repository may appear in multiple // categories and the metadata may be available from default repositories simultaneously. @@ -973,9 +1021,8 @@ QSet<Repository> MetadataJob::getRepositories() // Fetch repositories under archive which are selected in UI. // If repository is already fetched, do not fetch it again. - // In updater mode, fetch always all archive repositories to get updates for (const RepositoryCategory &repositoryCategory : m_core->settings().repositoryCategories()) { - if (!m_core->isUpdater() && !repositoryCategory.isEnabled()) + if (!repositoryCategory.isEnabled()) continue; for (const Repository &repository : repositoryCategory.repositories()) { @@ -1015,7 +1062,7 @@ bool MetadataJob::parsePackageUpdate(const QDomNodeList &c2, QString &packageNam else if ((element.tagName() == QLatin1String("SHA1")) && testCheckSum) packageHash = element.text(); else { - foreach (QString meta, *scMetaElements) { + foreach (QString meta, scMetaElements) { if (element.tagName() == meta) { metaFound = true; break; @@ -1122,4 +1169,14 @@ MetadataJob::Status MetadataJob::setAdditionalRepositories(QMultiHash<QString, Q } return status; } + +void MetadataJob::setInfoMessage(const QString &message) +{ + m_taskNumber++; + QString metaInformation = message; + if (m_totalTaskCount > 1) + metaInformation = QLatin1String(" %1 %2/%3 ").arg(message).arg(m_taskNumber).arg(m_totalTaskCount); + emit infoMessage(this, metaInformation); + +} } // namespace QInstaller diff --git a/src/libs/installer/metadatajob.h b/src/libs/installer/metadatajob.h index 37d9367e5..13ad3ea8c 100644 --- a/src/libs/installer/metadatajob.h +++ b/src/libs/installer/metadatajob.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -33,7 +33,7 @@ #include "fileutils.h" #include "job.h" #include "metadata.h" -#include "genericdatacache.h" +#include "metadatacache.h" #include "repository.h" #include <QFutureWatcher> @@ -74,6 +74,7 @@ public: bool resetCache(bool init = false); bool clearCache(); + bool isValidCache() const; private slots: void doStart() override; @@ -86,7 +87,7 @@ private slots: void progressChanged(int progress); void setProgressTotalAmount(int maximum); void unzipRepositoryTaskFinished(); - void startXMLTask(const QList<FileTaskItem> &items); + bool startXMLTask(); private: bool fetchMetaDataPackages(); @@ -108,6 +109,7 @@ private: const FileTaskResult &result, const Metadata &metadata); MetadataJob::Status setAdditionalRepositories(QMultiHash<QString, QPair<Repository, Repository> > repositoryUpdates, const FileTaskResult &result, const Metadata& metadata); + void setInfoMessage(const QString &message); private: friend class Metadata; @@ -116,6 +118,7 @@ private: PackageManagerCore *m_core; QList<FileTaskItem> m_packages; + QList<FileTaskItem> m_updatesXmlItems; TempPathDeleter m_tempDirDeleter; QFutureWatcher<FileTaskResult> m_xmlTask; QFutureWatcher<FileTaskResult> m_metadataTask; @@ -123,8 +126,8 @@ private: QHash<QFutureWatcher<void> *, QObject*> m_unzipTasks; QHash<QFutureWatcher<void> *, QObject*> m_unzipRepositoryTasks; DownloadType m_downloadType; - QList<FileTaskItem> m_unzipRepositoryitems; QList<FileTaskResult> m_metadataResult; + QList<FileTaskResult> m_updatesXmlResult; int m_downloadableChunkSize; int m_taskNumber; int m_totalTaskCount; @@ -133,7 +136,7 @@ private: QSet<Repository> m_fetchedCategorizedRepositories; QHash<QString, Metadata *> m_fetchedMetadata; - GenericDataCache<Metadata> m_metaFromCache; + MetadataCache m_metaFromCache; }; } // namespace QInstaller diff --git a/src/libs/installer/metadatajob_p.h b/src/libs/installer/metadatajob_p.h index ef2729dbb..837a7e9ae 100644 --- a/src/libs/installer/metadatajob_p.h +++ b/src/libs/installer/metadatajob_p.h @@ -62,11 +62,16 @@ class UnzipArchiveTask : public AbstractTask<void> public: UnzipArchiveTask(const QString &arcive, const QString &target) - : m_archive(arcive), m_targetDir(target), m_removeArchive(false) + : m_archive(arcive) + , m_targetDir(target) + , m_removeArchive(false) + , m_storeChecksums(false) {} + QString target() { return m_targetDir; } QString archive() { return m_archive; } void setRemoveArchive(bool remove) { m_removeArchive = remove; } + void setStoreChecksums(bool store) { m_storeChecksums = store; } void doTask(QFutureInterface<void> &fi) override { @@ -82,12 +87,43 @@ public: if (!archive) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Unsupported archive \"%1\": no handler " "registered for file suffix \"%2\".").arg(m_archive, QFileInfo(m_archive).suffix()))); + return; } else if (!archive->open(QIODevice::ReadOnly)) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for " "reading: %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString()))); + return; } else if (!archive->extract(m_targetDir)) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting " "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString()))); + return; + } + + if (m_storeChecksums) { + // Calculate and store checksums of extracted files for later use + const QVector<ArchiveEntry> entries = archive->list(); + for (auto &entry : entries) { + if (entry.isDirectory) + continue; + + QFile file(m_targetDir + QDir::separator() + entry.path); + if (!file.open(QIODevice::ReadOnly)) { + fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open extracted file \"%1\" for " + "reading: %2").arg(QDir::toNativeSeparators(file.fileName()), file.errorString()))); + break; + } + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + const QByteArray hexChecksum = hash.result().toHex(); + QFileInfo fileInfo(file.fileName()); + QFile hashFile(fileInfo.absolutePath() + QDir::separator() + + QString::fromLatin1(hexChecksum) + QLatin1String(".sha1")); + if (!hashFile.open(QIODevice::WriteOnly)) { + fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for " + "writing: %2").arg(QDir::toNativeSeparators(hashFile.fileName()), hashFile.errorString()))); + break; + } + } } archive->close(); @@ -101,6 +137,7 @@ private: QString m_archive; QString m_targetDir; bool m_removeArchive; + bool m_storeChecksums; }; class CacheTaskException : public QException @@ -126,7 +163,7 @@ class UpdateCacheTask : public AbstractTask<void> Q_DISABLE_COPY(UpdateCacheTask) public: - UpdateCacheTask(GenericDataCache<Metadata> &cache, QHash<QString, Metadata *> &updates) + UpdateCacheTask(MetadataCache &cache, QHash<QString, Metadata *> &updates) : m_cache(&cache) , m_updates(&updates) {} @@ -140,7 +177,7 @@ public: QStringList registeredKeys; bool success = true; for (auto *meta : qAsConst(*m_updates)) { - if (!m_cache->registerItem(meta, true)) { + if (!m_cache->registerItem(meta, true, MetadataCache::Move)) { success = false; break; } @@ -174,7 +211,7 @@ public: } private: - GenericDataCache<Metadata> *const m_cache; + MetadataCache *const m_cache; QHash<QString, Metadata *> *const m_updates; }; diff --git a/src/libs/installer/observer.cpp b/src/libs/installer/observer.cpp index 30afce719..57b67d8e1 100644 --- a/src/libs/installer/observer.cpp +++ b/src/libs/installer/observer.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -121,9 +121,9 @@ QByteArray FileTaskObserver::checkSum() const return m_hash.result(); } -void FileTaskObserver::addCheckSumData(const char *data, int length) +void FileTaskObserver::addCheckSumData(const QByteArray &data) { - m_hash.addData(data, length); + m_hash.addData(data); } void FileTaskObserver::addSample(qint64 sample) diff --git a/src/libs/installer/observer.h b/src/libs/installer/observer.h index 198a0f89c..d638d8ee4 100644 --- a/src/libs/installer/observer.h +++ b/src/libs/installer/observer.h @@ -60,7 +60,7 @@ public: QString progressText() const override; QByteArray checkSum() const; - void addCheckSumData(const char *data, int length); + void addCheckSumData(const QByteArray &data); void addSample(qint64 sample); void timerEvent(QTimerEvent *event) override; diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index b7d7d2a26..0eae41ea3 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -31,6 +31,7 @@ #include "adminauthorization.h" #include "binarycontent.h" #include "component.h" +#include "componentalias.h" #include "componentmodel.h" #include "downloadarchivesjob.h" #include "errors.h" @@ -46,6 +47,7 @@ #include "installercalculator.h" #include "uninstallercalculator.h" #include "loggingutils.h" +#include "componentsortfilterproxymodel.h" #include <productkeycheck.h> @@ -56,9 +58,15 @@ #include <QtCore/QMutex> #include <QtCore/QSettings> #include <QtCore/QTemporaryFile> +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include <QtCore5Compat/QTextCodec> +#include <QtCore5Compat/QTextDecoder> +#include <QtCore5Compat/QTextEncoder> +#else #include <QtCore/QTextCodec> #include <QtCore/QTextDecoder> #include <QtCore/QTextEncoder> +#endif #include <QtCore/QTextStream> #include <QDesktopServices> @@ -138,6 +146,8 @@ using namespace QInstaller; Installation has to be updated. \value EssentialUpdated Installation essential components were updated. + \value NoPackagesFound + No packages found from remote. */ /*! @@ -175,25 +185,6 @@ using namespace QInstaller; Emitted when the new root component \a comp is added. \sa {installer::componentAdded}{installer.componentAdded} - \sa rootComponentsAdded(), updaterComponentsAdded() -*/ - -/*! - \fn QInstaller::PackageManagerCore::rootComponentsAdded(QList<QInstaller::Component*> components) - - Emitted when the list of root components specified by \a components is added. - - \sa {installer::rootComponentsAdded}{installer.rootComponentsAdded} - \sa componentAdded(), updaterComponentsAdded() -*/ - -/*! - \fn QInstaller::PackageManagerCore::updaterComponentsAdded(QList<QInstaller::Component*> components) - - Emitted when a new list of updater components specified by \a components is added. - - \sa {installer::updaterComponentsAdded}{installer.updaterComponentsAdded} - \sa componentAdded(), rootComponentsAdded() */ /*! @@ -213,7 +204,7 @@ using namespace QInstaller; */ /*! - \fn QInstaller::PackageManagerCore::defaultTranslationsLoadedForLanguage(QLocale::Language lang) + \fn QInstaller::PackageManagerCore::defaultTranslationsLoadedForLanguage(QLocale lang) Emitted when the language \a lang has changed. @@ -600,6 +591,14 @@ bool PackageManagerCore::clearLocalCache(QString *error) } /*! + Returns \c true if the metadata cache is initialized and valid, \c false otherwise. +*/ +bool PackageManagerCore::isValidCache() const +{ + return d->m_metadataJob.isValidCache(); +} + +/*! \internal */ template <typename T> @@ -612,6 +611,22 @@ template bool PackageManagerCore::loadComponentScripts<QList<Component *>>(const template bool PackageManagerCore::loadComponentScripts<QHash<QString, Component *>>(const QHash<QString, Component *> &, const bool); /*! + Saves the installer \a args user has given when running installer. Command and option arguments + are not saved. +*/ +void PackageManagerCore::saveGivenArguments(const QStringList &args) +{ + m_arguments = args; +} + +/*! + Returns the commands and options user has given when running installer. +*/ +QStringList PackageManagerCore::givenArguments() const +{ + return m_arguments; +} +/*! \deprecated [4.5] Use recalculateAllComponents() instead. \sa {installer::componentsToInstallNeedsRecalculation}{installer.componentsToInstallNeedsRecalculation} @@ -639,6 +654,12 @@ void PackageManagerCore::componentsToInstallNeedsRecalculation() */ bool PackageManagerCore::recalculateAllComponents() { + // Clear previous results first, as the check states are updated + // at the end of both calculate methods, which refer to the results + // from both calculators. Needed to keep the state correct. + d->clearInstallerCalculator(); + d->clearUninstallerCalculator(); + if (!calculateComponentsToInstall()) return false; if (!isInstaller() && !calculateComponentsToUninstall()) @@ -792,12 +813,13 @@ quint64 PackageManagerCore::requiredDiskSpace() const */ quint64 PackageManagerCore::requiredTemporaryDiskSpace() const { - if (isOfflineOnly()) - return 0; - quint64 result = 0; - foreach (QInstaller::Component *component, orderedComponentsToInstall()) + foreach (QInstaller::Component *component, orderedComponentsToInstall()) { + if (!component->isFromOnlineRepository()) + continue; + result += size(component, scCompressedSize); + } return result; } @@ -833,7 +855,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize) ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + tr("Downloading packages...")); - DownloadArchivesJob archivesJob(this); + DownloadArchivesJob archivesJob(this, QLatin1String("downloadArchiveJob")); archivesJob.setAutoDelete(false); archivesJob.setArchivesToDownload(archivesToDownload); archivesJob.setExpectedTotalSize(archivesToDownloadTotalSize); @@ -1026,8 +1048,7 @@ QString PackageManagerCore::readFile(const QString &filePath, const QString &cod return QString(); QTextStream stream(&f); - stream.setCodec(codec); - return stream.readAll(); + return QString::fromUtf8(codec->fromUnicode(stream.readAll())); } /*! @@ -1516,7 +1537,7 @@ bool PackageManagerCore::fetchLocalPackagesTree() continue; } - QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + std::unique_ptr<QInstaller::Component> component(new QInstaller::Component(this)); component->loadDataFromPackage(package); QString name = component->treeName(); if (components.contains(name)) { @@ -1542,7 +1563,7 @@ bool PackageManagerCore::fetchLocalPackagesTree() if (!treeName.isEmpty()) treeNameComponents.insert(component->name(), treeName); - components.insert(name, component.take()); + components.insert(name, component.release()); } // Second pass with leftover packages if (firstRun) @@ -1587,10 +1608,11 @@ void PackageManagerCore::networkSettingsChanged() cancelMetaInfoJob(); d->m_updates = false; + d->m_aliases = false; d->m_repoFetched = false; d->m_updateSourcesAdded = false; - if (isMaintainer() ) { + if (!isInstaller()) { bool gainedAdminRights = false; if (!directoryWritable(d->targetDir())) { gainAdminRights(); @@ -1663,11 +1685,42 @@ bool PackageManagerCore::fetchCompressedPackagesTree() return fetchPackagesTree(packages, installedPackages); } +bool PackageManagerCore::fetchPackagesWithFallbackRepositories(const QStringList& components, bool &fallBackReposFetched) +{ + auto checkComponents = [&]() { + if (!fetchRemotePackagesTree(components)) + return false; + return true; + }; + + if (!checkComponents()) { + // error when fetching packages tree + if (status() != NoPackagesFound) + return false; + //retry fetching packages with all categories enabled + fallBackReposFetched = true; + if (!d->enableAllCategories()) + return false; + + qCDebug(QInstaller::lcInstallerInstallLog).noquote() + << "Components not found with the current selection." + << "Searching from additional repositories"; + if (!ProductKeyCheck::instance()->securityWarning().isEmpty()) { + qCWarning(QInstaller::lcInstallerInstallLog) << ProductKeyCheck::instance()->securityWarning(); + } + if (!checkComponents()) { + return false; + } + } + return true; +} + /*! Checks for packages to install. Returns \c true if newer versions exist - and they can be installed. + and they can be installed. Returns \c false if not \a components are found + for install, or if error occurred when fetching and generating package tree. */ -bool PackageManagerCore::fetchRemotePackagesTree() +bool PackageManagerCore::fetchRemotePackagesTree(const QStringList& components) { d->setStatus(Running); @@ -1695,8 +1748,15 @@ bool PackageManagerCore::fetchRemotePackagesTree() return false; const PackagesList &packages = d->remotePackages(); - if (packages.isEmpty()) + if (packages.isEmpty()) { + d->setStatus(PackageManagerCore::NoPackagesFound); + return false; + } + + if (!d->installablePackagesFound(components)) return false; + + d->m_componentsToBeInstalled = components; return fetchPackagesTree(packages, installedPackages); } @@ -1945,6 +2005,82 @@ void PackageManagerCore::setTemporaryRepositories(const QStringList &repositorie settings().setTemporaryRepositories(repositorySet, replace); } +bool PackageManagerCore::addQBspRepositories(const QStringList &repositories) +{ + QSet<Repository> set; + foreach (QString fileName, repositories) { + Repository repository = Repository::fromUserInput(fileName, true); + repository.setEnabled(true); + set.insert(repository); + } + if (set.count() > 0) { + settings().addTemporaryRepositories(set, false); + return true; + } + return false; +} + +bool PackageManagerCore::validRepositoriesAvailable() const +{ + foreach (const Repository &repo, settings().repositories()) { + if (repo.isEnabled() && repo.isValid()) { + return true; + } + } + return false; +} + +void PackageManagerCore::setAllowCompressedRepositoryInstall(bool allow) +{ + d->m_allowCompressedRepositoryInstall = allow; +} + +bool PackageManagerCore::allowCompressedRepositoryInstall() const +{ + return d->m_allowCompressedRepositoryInstall; +} + +bool PackageManagerCore::showRepositoryCategories() const +{ + bool showCagetories = settings().repositoryCategories().count() > 0 && !isOfflineOnly() && !isUpdater(); + if (showCagetories) + settings().setAllowUnstableComponents(true); + return showCagetories; +} + +QVariantMap PackageManagerCore::organizedRepositoryCategories() const +{ + QVariantMap map; + QSet<RepositoryCategory> categories = settings().repositoryCategories(); + foreach (const RepositoryCategory &category, categories) + map.insert(category.displayname(), QVariant::fromValue(category)); + return map; +} + +void PackageManagerCore::enableRepositoryCategory(const QString &repositoryName, bool enable) +{ + QMap<QString, RepositoryCategory> organizedRepositoryCategories = settings().organizedRepositoryCategories(); + + QMap<QString, RepositoryCategory>::iterator i = organizedRepositoryCategories.find(repositoryName); + while (i != organizedRepositoryCategories.end() && i.key() == repositoryName) { + d->enableRepositoryCategory(i.value(), enable); + i++; + } +} + +void PackageManagerCore::runProgram() +{ + const QString program = replaceVariables(value(scRunProgram)); + + const QStringList args = replaceVariables(values(scRunProgramArguments)); + if (program.isEmpty()) + return; + + qCDebug(QInstaller::lcInstallerInstallLog) << "starting" << program << args; + QProcess::startDetached(program, args); +} + + /*! Returns the script engine that prepares and runs the component scripts. @@ -2102,6 +2238,15 @@ Component *PackageManagerCore::componentByName(const QString &name) const } /*! + Searches for a component alias matching \a name and returns it. + If no alias matches the name, \c nullptr is returned. +*/ +ComponentAlias *PackageManagerCore::aliasByName(const QString &name) const +{ + return d->m_componentAliases.value(name); +} + +/*! Searches \a components for a component matching \a name and returns it. \a name can also contain a version requirement. For example, \c org.qt-project.sdk.qt returns any component with that name, whereas \c{org.qt-project.sdk.qt->=4.5} requires @@ -2127,6 +2272,18 @@ Component *PackageManagerCore::componentByName(const QString &name, const QList< } /*! + Returns an array of all components currently available. If the repository + metadata have not been fetched yet, the array will be empty. Optionally, a + \a regexp expression can be used to further filter the listed packages. + + \sa {installer::components}{installer.components} + */ +QList<Component *> PackageManagerCore::components(const QString ®exp) const +{ + return components(PackageManagerCore::ComponentType::All, regexp); +} + +/*! Returns \c true if directory specified by \a path is writable by the current user. */ @@ -2164,8 +2321,26 @@ QList<Component *> PackageManagerCore::componentsMarkedForInstallation() const } /*! - Determines which components to install based on the current run mode, including dependencies - and automatic dependencies. Returns \c true on success, \c false otherwise. + Returns a list of component aliases that are marked for installation. + The list can be empty. +*/ +QList<ComponentAlias *> PackageManagerCore::aliasesMarkedForInstallation() const +{ + if (isUpdater()) // Aliases not supported on update at the moment + return QList<ComponentAlias *>(); + + QList<ComponentAlias *> markedForInstallation; + for (auto *alias : qAsConst(d->m_componentAliases)) { + if (alias && alias->isSelected()) + markedForInstallation.append(alias); + } + + return markedForInstallation; +} + +/*! + Determines which components to install based on the current run mode, including component aliases, + dependencies and automatic dependencies. Returns \c true on success, \c false otherwise. The aboutCalculateComponentsToInstall() signal is emitted before the calculation starts, the finishedCalculateComponentsToInstall() @@ -2179,15 +2354,13 @@ bool PackageManagerCore::calculateComponentsToInstall() const emit aboutCalculateComponentsToInstall(); d->clearInstallerCalculator(); - const QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation(); - const bool componentsToInstallCalculated = - d->installerCalculator()->solve(selectedComponentsToInstall); + const bool calculated = d->installerCalculator()->solve(); d->updateComponentInstallActions(); emit finishedCalculateComponentsToInstall(); - return componentsToInstallCalculated; + return calculated; } /*! @@ -2455,12 +2628,13 @@ ComponentModel *PackageManagerCore::defaultComponentModel() const if (!d->m_defaultModel) { d->m_defaultModel = componentModel(const_cast<PackageManagerCore*> (this), QLatin1String("AllComponentsModel")); + + connect(this, &PackageManagerCore::startAllComponentsReset, [&] { + d->m_defaultModel->reset(); + }); + connect(this, &PackageManagerCore::finishAllComponentsReset, d->m_defaultModel, + &ComponentModel::reset); } - connect(this, &PackageManagerCore::startAllComponentsReset, [&] { - d->m_defaultModel->reset(); - }); - connect(this, &PackageManagerCore::finishAllComponentsReset, d->m_defaultModel, - &ComponentModel::reset); return d->m_defaultModel; } @@ -2473,44 +2647,70 @@ ComponentModel *PackageManagerCore::updaterComponentModel() const if (!d->m_updaterModel) { d->m_updaterModel = componentModel(const_cast<PackageManagerCore*> (this), QLatin1String("UpdaterComponentsModel")); + + connect(this, &PackageManagerCore::startUpdaterComponentsReset, [&] { + d->m_updaterModel->reset(); + }); + connect(this, &PackageManagerCore::finishUpdaterComponentsReset, d->m_updaterModel, + &ComponentModel::reset); } - connect(this, &PackageManagerCore::startUpdaterComponentsReset, [&] { - d->m_updaterModel->reset(); - }); - connect(this, &PackageManagerCore::finishUpdaterComponentsReset, d->m_updaterModel, - &ComponentModel::reset); return d->m_updaterModel; } /*! + Returns the proxy model +*/ + +ComponentSortFilterProxyModel *PackageManagerCore::componentSortFilterProxyModel() +{ + if (!d->m_componentSortFilterProxyModel) { + d->m_componentSortFilterProxyModel = new ComponentSortFilterProxyModel(this); + d->m_componentSortFilterProxyModel->setRecursiveFilteringEnabled(true); + d->m_componentSortFilterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + } + return d->m_componentSortFilterProxyModel; +} + +/*! Lists available packages filtered with \a regexp without GUI. Virtual 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. + Returns \c true if matching packages were found, \c false otherwise. + \sa setVirtualComponentsVisible() */ -void PackageManagerCore::listAvailablePackages(const QString ®exp, const QHash<QString, QString> &filters) +bool PackageManagerCore::listAvailablePackages(const QString ®exp, const QHash<QString, QString> &filters) { setPackageViewer(); + d->enableAllCategories(); qCDebug(QInstaller::lcInstallerInstallLog) << "Searching packages with regular expression:" << regexp; ComponentModel *model = defaultComponentModel(); - d->fetchMetaInformationFromRepositories(); + PackagesList packages; + + if (!d->m_updates) { + d->fetchMetaInformationFromRepositories(); + d->addUpdateResourcesFromRepositories(); + + packages = d->remotePackages(); + if (!fetchAllPackages(packages, LocalPackagesMap())) { + qCWarning(QInstaller::lcInstallerInstallLog) + << "There was a problem with loading the package data."; + return false; + } + } else { + // No need to fetch metadata again + packages = d->remotePackages(); + } - d->addUpdateResourcesFromRepositories(); QRegularExpression re(regexp); re.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - const PackagesList &packages = d->remotePackages(); - if (!fetchAllPackages(packages, LocalPackagesMap())) { - qCWarning(QInstaller::lcInstallerInstallLog) - << "There was a problem with loading the package data."; - return; - } PackagesList matchedPackages; - foreach (Package *package, packages) { + foreach (Package *package, qAsConst(packages)) { const QString name = package->data(scName).toString(); Component *component = componentByName(name); if (!component) @@ -2532,10 +2732,56 @@ void PackageManagerCore::listAvailablePackages(const QString ®exp, const QHas matchedPackages.append(package); } } - if (matchedPackages.count() == 0) + if (matchedPackages.count() == 0) { qCDebug(QInstaller::lcInstallerInstallLog) << "No matching packages found."; - else - LoggingHandler::instance().printPackageInformation(matchedPackages, localInstalledPackages()); + return false; + } + + LoggingHandler::instance().printPackageInformation(matchedPackages, localInstalledPackages()); + return true; +} + +/*! + Lists available component aliases filtered with \a regexp without GUI. Virtual + aliases are not listed unless set visible. + + Returns \c true if matching package aliases were found, \c false otherwise. + + \sa setVirtualComponentsVisible() +*/ +bool PackageManagerCore::listAvailableAliases(const QString ®exp) +{ + setPackageViewer(); + d->enableAllCategories(); + qCDebug(QInstaller::lcInstallerInstallLog) + << "Searching aliases with regular expression:" << regexp; + + if (!d->buildComponentAliases()) + return false; + + QRegularExpression re(regexp); + re.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + + QList<ComponentAlias *> matchedAliases; + for (auto *alias : std::as_const(d->m_componentAliases)) { + if (!alias) + continue; + + if (re.match(alias->name()).hasMatch()) { + if (alias->isVirtual() && !virtualComponentsVisible()) + continue; + + matchedAliases.append(alias); + } + } + + if (matchedAliases.isEmpty()) { + qCDebug(QInstaller::lcInstallerInstallLog) << "No matching package aliases found."; + return false; + } + + LoggingHandler::instance().printAliasInformation(matchedAliases); + return true; } bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &componentName) @@ -2549,7 +2795,7 @@ bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &co } ComponentModel *model = defaultComponentModel(); const QModelIndex &idx = model->indexFromComponentName(component->treeName()); - if (model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) { + if (model->data(idx, Qt::CheckStateRole) == QVariant()) { // Component cannot be unselected, check why if (component->forcedInstallation()) { qCWarning(QInstaller::lcInstallerInstallLog).noquote().nospace() @@ -2573,25 +2819,50 @@ bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &co /*! \internal - Tries to set \c Qt::CheckStateRole to \c Qt::Checked for given \a components in the - default component model. Returns \c true if \a components contains at least one component + Tries to set \c Qt::CheckStateRole to \c Qt::Checked for given component \a names in the + default component model, and select given aliases in the \c names list. + + Returns \c true if \a names contains at least one component or component alias eligible for installation, otherwise returns \c false. An error message can be retrieved with \a errorMessage. */ -bool PackageManagerCore::checkComponentsForInstallation(const QStringList &components, QString &errorMessage) +bool PackageManagerCore::checkComponentsForInstallation(const QStringList &names, QString &errorMessage, bool &unstableAliasFound, bool fallbackReposFetched) { bool installComponentsFound = false; ComponentModel *model = defaultComponentModel(); - foreach (const QString &name, components) { + foreach (const QString &name, names) { Component *component = componentByName(name); if (!component) { - errorMessage.append(tr("Cannot install %1. Component not found.").arg(name) + QLatin1Char('\n')); + // No such component, check if we have an alias by the name + if (ComponentAlias *alias = aliasByName(name)) { + if (alias->isUnstable()) { + errorMessage.append(tr("Cannot select alias %1. There was a problem loading this alias, " + "so it is marked unstable and cannot be selected.").arg(name) + QLatin1Char('\n')); + unstableAliasFound = true; + setCanceled(); + return false; + } else if (alias->isVirtual()) { + errorMessage.append(tr("Cannot select %1. Alias is marked virtual, meaning it cannot " + "be selected manually.").arg(name) + QLatin1Char('\n')); + continue; + } else if (alias->missingOptionalComponents() && !fallbackReposFetched) { + unstableAliasFound = true; + setCanceled(); + return false; + } + + alias->setSelected(true); + installComponentsFound = true; + } else { + errorMessage.append(tr("Cannot install %1. Component not found.").arg(name) + QLatin1Char('\n')); + } + continue; } const QModelIndex &idx = model->indexFromComponentName(component->treeName()); if (idx.isValid()) { - if ((model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) && !component->forcedInstallation()) { + if ((model->data(idx, Qt::CheckStateRole) == QVariant()) && !component->forcedInstallation()) { // User cannot select the component, check why if (component->autoDependencies().count() > 0) { errorMessage.append(tr("Cannot install component %1. Component is installed only as automatic " @@ -2661,6 +2932,25 @@ void PackageManagerCore::listInstalledPackages(const QString ®exp) LoggingHandler::instance().printLocalPackageInformation(packages); } +PackageManagerCore::Status PackageManagerCore::searchAvailableUpdates() +{ + setUpdater(); + d->enableAllCategories(); + if (!fetchRemotePackagesTree()) { + qCWarning(QInstaller::lcInstallerInstallLog) << error(); + return status(); + } + + const QList<QInstaller::Component *> availableUpdates = + components(QInstaller::PackageManagerCore::ComponentType::Root); + if (availableUpdates.isEmpty()) { + qCWarning(QInstaller::lcInstallerInstallLog) << "There are currently no updates available."; + return status(); + } + QInstaller::LoggingHandler::instance().printUpdateInformation(availableUpdates); + return status(); +} + /*! Updates the selected components \a componentsToUpdate without GUI. If essential components are found, then only those will be updated. @@ -2668,13 +2958,25 @@ void PackageManagerCore::listInstalledPackages(const QString ®exp) */ PackageManagerCore::Status PackageManagerCore::updateComponentsSilently(const QStringList &componentsToUpdate) { - if (d->runningProcessesFound()) - throw Error(tr("Running processes found.")); setUpdater(); ComponentModel *model = updaterComponentModel(); - fetchRemotePackagesTree(); + if (componentsToUpdate.isEmpty()) { + d->enableAllCategories(); + fetchRemotePackagesTree(); + } else { + bool fallbackReposFetched = false; + bool packagesFound = fetchPackagesWithFallbackRepositories(componentsToUpdate, fallbackReposFetched); + + if (!packagesFound) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() + << "No components available for update with the current selection."; + d->setStatus(Canceled); + return status(); + } + } + // List contains components containing update, if essential found contains only essential component const QList<QInstaller::Component*> componentList = componentsMarkedForInstallation(); @@ -2780,6 +3082,20 @@ void PackageManagerCore::addLicenseItem(const QHash<QString, QVariantMap> &licen } } +bool PackageManagerCore::hasLicenses() const +{ + foreach (Component* component, orderedComponentsToInstall()) { + if (isMaintainer() && component->isInstalled()) + continue; // package manager or updater, hide as long as the component is installed + + // The component is about to be installed and provides a license, so the page needs to + // be shown. + if (!component->licenses().isEmpty()) + return true; + } + return false; +} + /*! * Adds \a component local \a dependencies to a hash table for quicker search for * uninstall dependency components. @@ -2804,9 +3120,6 @@ void PackageManagerCore::createAutoDependencyHash(const QString &component, cons */ PackageManagerCore::Status PackageManagerCore::uninstallComponentsSilently(const QStringList& components) { - if (d->runningProcessesFound()) - throw Error(tr("Running processes found.")); - if (components.isEmpty()) { qCDebug(QInstaller::lcInstallerInstallLog) << "No components selected for uninstallation."; return PackageManagerCore::Canceled; @@ -2845,8 +3158,6 @@ PackageManagerCore::Status PackageManagerCore::uninstallComponentsSilently(const PackageManagerCore::Status PackageManagerCore::removeInstallationSilently() { setCompleteUninstallation(true); - if (d->runningProcessesFound()) - throw Error(tr("Running processes found.")); qCDebug(QInstaller::lcInstallerInstallLog) << "Complete uninstallation was chosen."; if (!(d->m_autoConfirmCommand || d->askUserConfirmCommand())) { @@ -2868,23 +3179,7 @@ PackageManagerCore::Status PackageManagerCore::removeInstallationSilently() PackageManagerCore::Status PackageManagerCore::createOfflineInstaller(const QStringList &componentsToAdd) { setOfflineGenerator(); - // init default model before fetching remote packages tree - ComponentModel *model = defaultComponentModel(); - Q_UNUSED(model); - if (!fetchRemotePackagesTree()) - return status(); - - QString errorMessage; - if (checkComponentsForInstallation(componentsToAdd, errorMessage)) { - if (d->calculateComponentsAndRun()) { - qCDebug(QInstaller::lcInstallerInstallLog) - << "Created installer to:" << offlineBinaryName(); - } - } else { - qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage - << "\nNo components available with the current selection."; - } - return status(); + return d->fetchComponentsAndInstall(componentsToAdd); } /*! @@ -2896,9 +3191,6 @@ PackageManagerCore::Status PackageManagerCore::createOfflineInstaller(const QStr PackageManagerCore::Status PackageManagerCore::installSelectedComponentsSilently(const QStringList& components) { if (!isInstaller()) { - // Check if there are processes running in the install if maintenancetool is used. - if (d->runningProcessesFound()) - throw Error(tr("Running processes found.")); setPackageManager(); //Check that packages are not already installed @@ -2911,24 +3203,7 @@ PackageManagerCore::Status PackageManagerCore::installSelectedComponentsSilently return PackageManagerCore::Canceled; } } - - // init default model before fetching remote packages tree - ComponentModel *model = defaultComponentModel(); - Q_UNUSED(model); - if (!fetchRemotePackagesTree()) - return status(); - - QString errorMessage; - if (checkComponentsForInstallation(components, errorMessage)) { - if (!errorMessage.isEmpty()) - qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage; - if (d->calculateComponentsAndRun()) - qCDebug(QInstaller::lcInstallerInstallLog) << "Components installed successfully"; - } else { - qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage - << "\nNo components available for installation with the current selection."; - } - return status(); + return d->fetchComponentsAndInstall(components); } /*! @@ -2994,6 +3269,21 @@ void PackageManagerCore::dropAdminRights() } /*! + Returns \c true if the installer has admin rights. For example, if the installer + was started with a root account, or the internal admin rights elevation is active. + + Returns \c false otherwise. + + \sa {installer::hasAdminRights}{installer.hasAdminRights} + \sa gainAdminRights() + \sa dropAdminRights() +*/ +bool PackageManagerCore::hasAdminRights() const +{ + return AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive(); +} + +/*! Sets checkAvailableSpace based on value of \a check. */ void PackageManagerCore::setCheckAvailableSpace(bool check) @@ -3002,12 +3292,22 @@ void PackageManagerCore::setCheckAvailableSpace(bool check) } /*! - Checks available disk space if the feature is not explicitly disabled. Informative - text about space status can be retrieved by passing \a message parameter. Returns + * Returns informative text about disk space status + */ +QString PackageManagerCore::availableSpaceMessage() const +{ + return m_availableSpaceMessage; +} + +/*! + Checks available disk space if the feature is not explicitly disabled. Returns \c true if there is sufficient free space on installation and temporary volumes. + + \sa availableSpaceMessage() */ -bool PackageManagerCore::checkAvailableSpace(QString &message) const +bool PackageManagerCore::checkAvailableSpace() { + m_availableSpaceMessage.clear(); const quint64 extraSpace = 256 * 1024 * 1024LL; quint64 required(requiredDiskSpace()); quint64 tempRequired(requiredTemporaryDiskSpace()); @@ -3063,21 +3363,21 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const } if (cacheOnSameVolume && (installVolumeAvailableSize <= (required + tempRequired))) { - message = tr("Not enough disk space to store temporary files and the " + m_availableSpaceMessage = tr("Not enough disk space to store temporary files and the " "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 " + m_availableSpaceMessage = tr("Not enough disk space to store all selected components! %1 are " "available, while the minimum required is %2.").arg(humanReadableSize(installVolumeAvailableSize), humanReadableSize(required)); return false; } if (cacheVolumeAvailableSize < tempRequired) { - message = tr("Not enough disk space to store temporary files! %1 are available, " + m_availableSpaceMessage = tr("Not enough disk space to store temporary files! %1 are available, " "while the minimum required is %2. You may select another location for the " "temporary files by modifying the local cache path from the installer settings.") .arg(humanReadableSize(cacheVolumeAvailableSize), humanReadableSize(tempRequired)); @@ -3086,22 +3386,25 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const if (installVolumeAvailableSize - required < 0.01 * targetVolume.size()) { // warn for less than 1% of the volume's space being free - message = tr("The volume you selected for installation seems to have sufficient space for " + m_availableSpaceMessage = tr("The volume you selected for installation seems to have sufficient space for " "installation, but there will be less than 1% of the volume's space available afterwards."); } else if (installVolumeAvailableSize - required < 100 * 1024 * 1024LL) { // warn for less than 100MB being free - message = tr("The volume you selected for installation seems to have sufficient " + m_availableSpaceMessage = tr("The volume you selected for installation seems to have sufficient " "space for installation, but there will be less than 100 MB available afterwards."); } #ifdef Q_OS_WIN if (isOfflineGenerator() && (required > UINT_MAX)) { - message = tr("The estimated installer size %1 would exceed the supported executable " + m_availableSpaceMessage = tr("The estimated installer size %1 would exceed the supported executable " "size limit of %2. The application may not be able to run.") .arg(humanReadableSize(required), humanReadableSize(UINT_MAX)); } #endif } - message = QString::fromLatin1("%1 %2").arg(message, tr("Installation will use %1 of disk space.") + m_availableSpaceMessage = QString::fromLatin1("%1 %2").arg(m_availableSpaceMessage, + (isOfflineGenerator() + ? tr("Created installer will use %1 of disk space.") + : tr("Installation will use %1 of disk space.")) .arg(humanReadableSize(requiredDiskSpace()))).simplified(); return true; @@ -3160,6 +3463,10 @@ bool PackageManagerCore::killProcess(const QString &absoluteFilePath) const } /*! + \deprecated [4.6] Maintenance tool no longer automatically checks for all running processes + in the installation directory for CLI runs. To manually check for a process to stop, use + \l {component::addStopProcessForUpdateRequest}{component.addStopProcessForUpdateRequest} instead. + Sets additional \a processes that can run when updating with the maintenance tool. @@ -3171,6 +3478,10 @@ void PackageManagerCore::setAllowedRunningProcesses(const QStringList &processes } /*! + \deprecated [4.6] Maintenance tool no longer automatically checks for all running processes + in the installation directory for CLI runs. To manually check for a process to stop, use + \l {component::addStopProcessForUpdateRequest}{component.addStopProcessForUpdateRequest} instead. + Returns processes that are allowed to run when updating with the maintenance tool. @@ -3686,6 +3997,14 @@ QString PackageManagerCore::offlineBinaryName() const } /*! + Add new \a source for looking component aliases. +*/ +void PackageManagerCore::addAliasSource(const AliasSource &source) +{ + d->m_aliasSources.insert(source); +} + +/*! \sa {installer::setInstaller}{installer.setInstaller} \sa isInstaller(), setUpdater(), setPackageManager() */ @@ -3787,6 +4106,7 @@ bool PackageManagerCore::isPackageManager() const void PackageManagerCore::setOfflineGenerator() { d->m_magicMarkerSupplement = BinaryContent::OfflineGenerator; + emit installerBinaryMarkerChanged(d->m_magicBinaryMarker); } /*! @@ -3805,6 +4125,7 @@ bool PackageManagerCore::isOfflineGenerator() const void PackageManagerCore::setPackageViewer() { d->m_magicMarkerSupplement = BinaryContent::PackageViewer; + emit installerBinaryMarkerChanged(d->m_magicBinaryMarker); } /*! @@ -3818,6 +4139,17 @@ bool PackageManagerCore::isPackageViewer() const } /*! + Resets the binary marker supplement of the installer to \c Default. + The supplement enables or disables additional features on top of the binary + marker state (\c Installer, \c Updater, \c PackageManager, \c Uninstaller). +*/ +void PackageManagerCore::resetBinaryMarkerSupplement() +{ + d->m_magicMarkerSupplement = BinaryContent::Default; + emit installerBinaryMarkerChanged(d->m_magicBinaryMarker); +} + +/*! Sets the installer magic binary marker based on \a magicMarker and userSetBinaryMarker to \c true. */ @@ -3992,13 +4324,6 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo component->setUninstalled(); const QString localPath = component->localTempPath(); - if (LoggingHandler::instance().verboseLevel() == LoggingHandler::Detailed) { - static QString lastLocalPath; - if (lastLocalPath != localPath) - qCDebug(QInstaller::lcDeveloperBuild()) << "Url is:" << localPath; - lastLocalPath = localPath; - } - const Repository repo = d->m_metadataJob.repositoryForCacheDirectory(localPath); if (repo.isValid()) { @@ -4130,10 +4455,10 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc continue; } - QScopedPointer<QInstaller::Component> remoteComponent(new QInstaller::Component(this)); + std::unique_ptr<QInstaller::Component> remoteComponent(new QInstaller::Component(this)); data.package = package; remoteComponent->loadDataFromPackage(*package); - if (updateComponentData(data, remoteComponent.data())) { + if (updateComponentData(data, remoteComponent.get())) { // Create a list where is name and treename. Repo can contain a package with // a different treename of component which is already installed. We don't want // to move already installed local packages. @@ -4141,7 +4466,7 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc if (!treeName.isEmpty()) remoteTreeNameComponents.insert(remoteComponent->name(), treeName); const QString name = remoteComponent->treeName(); - allComponents.insert(name, remoteComponent.take()); + allComponents.insert(name, remoteComponent.release()); } } // Second pass with leftover packages @@ -4164,7 +4489,7 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc d->m_localVirtualComponents.append(package.name); } - QScopedPointer<QInstaller::Component> localComponent(new QInstaller::Component(this)); + std::unique_ptr<QInstaller::Component> localComponent(new QInstaller::Component(this)); localComponent->loadDataFromPackage(package); const QString name = localComponent->treeName(); @@ -4219,7 +4544,7 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc const QString treeName = localComponent->value(scTreeName); if (!treeName.isEmpty()) allTreeNameComponents.insert(localComponent->name(), treeName); - allComponents.insert(name, localComponent.take()); + allComponents.insert(name, localComponent.release()); } // store all components that got a replacement @@ -4233,6 +4558,9 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc d->commitPendingUnstableComponents(); + if (!d->buildComponentAliases()) + return false; + } catch (const Error &error) { d->clearAllComponentLists(); d->setStatus(PackageManagerCore::Failure, error.message()); @@ -4270,12 +4598,12 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const if (!ProductKeyCheck::instance()->isValidPackage(update->data(scName).toString())) continue; - QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + std::unique_ptr<QInstaller::Component> component(new QInstaller::Component(this)); data.package = update; component->loadDataFromPackage(*update); - if (updateComponentData(data, component.data())) { + if (updateComponentData(data, component.get())) { // Keep a reference so we can resolve dependencies during update. - d->m_updaterComponentsDeps.append(component.take()); + d->m_updaterComponentsDeps.append(component.release()); // const QString isNew = update->data(scNewComponent).toString(); // if (isNew.toLower() != scTrue) diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index e5236112e..d9da87d85 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -29,6 +29,7 @@ #define PACKAGEMANAGERCORE_H #include "binaryformat.h" +#include "binarycontent.h" #include "component.h" #include "protocol.h" #include "repository.h" @@ -45,11 +46,14 @@ namespace QInstaller { +struct AliasSource; class ComponentModel; +class ComponentAlias; class ScriptEngine; class PackageManagerCorePrivate; class PackageManagerProxyFactory; class Settings; +class ComponentSortFilterProxyModel; // -- PackageManagerCore @@ -80,7 +84,8 @@ public: Canceled = 3, Unfinished = 4, ForceUpdate = 5, - EssentialUpdated = 6 + EssentialUpdated = 6, + NoPackagesFound = 7 }; Status status() const; QString error() const; @@ -145,8 +150,9 @@ public: void setProxyFactory(PackageManagerProxyFactory *factory); PackagesList remotePackages(); - bool fetchRemotePackagesTree(); + bool fetchRemotePackagesTree(const QStringList& components = QStringList()); bool fetchCompressedPackagesTree(); + bool fetchPackagesWithFallbackRepositories(const QStringList& components, bool &fallBackReposFetched); bool run(); void reset(); @@ -202,9 +208,20 @@ public: void setOfflineBinaryName(const QString &name); QString offlineBinaryName() const; + void addAliasSource(const AliasSource &source); + Q_INVOKABLE void addUserRepositories(const QStringList &repositories); Q_INVOKABLE void setTemporaryRepositories(const QStringList &repositories, bool replace = false, bool compressed = false); + bool addQBspRepositories(const QStringList &repositories); + bool validRepositoriesAvailable() const; + Q_INVOKABLE void setAllowCompressedRepositoryInstall(bool allow); + bool allowCompressedRepositoryInstall() const; + bool showRepositoryCategories() const; + QVariantMap organizedRepositoryCategories() const; + void enableRepositoryCategory(const QString &repositoryName, bool enable); + void runProgram(); + Q_INVOKABLE void autoAcceptMessageBoxes(); Q_INVOKABLE void autoRejectMessageBoxes(); Q_INVOKABLE void setMessageBoxAutomaticAnswer(const QString &identifier, int button); @@ -241,7 +258,10 @@ public: void appendUpdaterComponent(Component *components); QList<Component *> components(ComponentTypes mask, const QString ®exp = QString()) const; - Component *componentByName(const QString &identifier) const; + Q_INVOKABLE QInstaller::Component *componentByName(const QString &identifier) const; + Q_INVOKABLE QList<QInstaller::Component *> components(const QString ®exp = QString()) const; + + ComponentAlias *aliasByName(const QString &name) const; Q_INVOKABLE bool calculateComponentsToInstall() const; QList<Component*> orderedComponentsToInstall() const; @@ -252,6 +272,9 @@ public: Q_INVOKABLE bool calculateComponentsToUninstall() const; QList<Component*> componentsToUninstall() const; + QList<Component *> componentsMarkedForInstallation() const; + QList<ComponentAlias *> aliasesMarkedForInstallation() const; + QString componentsToInstallError() const; QString componentsToUninstallError() const; QString installReason(Component *component) const; @@ -263,9 +286,14 @@ public: ComponentModel *defaultComponentModel() const; ComponentModel *updaterComponentModel() const; + ComponentSortFilterProxyModel *componentSortFilterProxyModel(); + void listInstalledPackages(const QString ®exp = QString()); - void listAvailablePackages(const QString ®exp = QString(), + bool listAvailablePackages(const QString ®exp = QString(), const QHash<QString, QString> &filters = QHash<QString, QString>()); + bool listAvailableAliases(const QString ®exp = QString()); + + PackageManagerCore::Status searchAvailableUpdates(); PackageManagerCore::Status updateComponentsSilently(const QStringList &componentsToUpdate); PackageManagerCore::Status installSelectedComponentsSilently(const QStringList& components); PackageManagerCore::Status installDefaultComponentsSilently(); @@ -293,6 +321,8 @@ public: void setPackageViewer(); Q_INVOKABLE bool isPackageViewer() const; + void resetBinaryMarkerSupplement(); + void setUserSetBinaryMarker(qint64 magicMarker); Q_INVOKABLE bool isUserSetBinaryMarker() const; @@ -307,9 +337,11 @@ public: Q_INVOKABLE bool gainAdminRights(); Q_INVOKABLE void dropAdminRights(); + Q_INVOKABLE bool hasAdminRights() const; void setCheckAvailableSpace(bool check); - bool checkAvailableSpace(QString &message) const; + bool checkAvailableSpace(); + QString availableSpaceMessage() const; Q_INVOKABLE quint64 requiredDiskSpace() const; Q_INVOKABLE quint64 requiredTemporaryDiskSpace() const; @@ -353,14 +385,20 @@ public: void clearLicenses(); QHash<QString, QMap<QString, QString>> sortedLicenses(); void addLicenseItem(const QHash<QString, QVariantMap> &licenses); + bool hasLicenses() const; void createLocalDependencyHash(const QString &component, const QString &dependencies) const; void createAutoDependencyHash(const QString &component, const QString &oldDependencies, const QString &newDependencies) const; bool resetLocalCache(bool init = false); bool clearLocalCache(QString *error = nullptr); + bool isValidCache() const; + template <typename T> bool loadComponentScripts(const T &components, const bool postScript = false); + void saveGivenArguments(const QStringList &args); + QStringList givenArguments() const; + public Q_SLOTS: bool runInstaller(); bool runUninstaller(); @@ -380,11 +418,9 @@ Q_SIGNALS: void aboutCalculateComponentsToUninstall() const; void finishedCalculateComponentsToUninstall() const; void componentAdded(QInstaller::Component *comp); - void rootComponentsAdded(QList<QInstaller::Component*> components); - void updaterComponentsAdded(QList<QInstaller::Component*> components); void valueChanged(const QString &key, const QString &value); void statusChanged(QInstaller::PackageManagerCore::Status); - void defaultTranslationsLoadedForLanguage(QLocale::Language lang); + void defaultTranslationsLoadedForLanguage(QLocale lang); void currentPageChanged(int page); void finishButtonClicked(); @@ -447,17 +483,18 @@ private: QString findDisplayVersion(const QString &componentName, const QHash<QString, QInstaller::Component*> &components, const QString& versionKey, QHash<QString, bool> &visited); ComponentModel *componentModel(PackageManagerCore *core, const QString &objectName) const; - QList<Component *> componentsMarkedForInstallation() const; bool fetchPackagesTree(const PackagesList &packages, const LocalPackagesMap installedPackages); bool componentUninstallableFromCommandLine(const QString &componentName); - bool checkComponentsForInstallation(const QStringList &components, QString &errorMessage); + bool checkComponentsForInstallation(const QStringList &names, QString &errorMessage, bool &unstableAliasFound, bool fallbackReposFetched); private: PackageManagerCorePrivate *const d; friend class PackageManagerCorePrivate; QHash<QString, QString> m_fileDialogAutomaticAnswers; QHash<QString, QStringList> m_localVirtualWithDependants; + QString m_availableSpaceMessage; + QStringList m_arguments; private: // remove once we deprecate isSelected, setSelected etc... diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 259aafeb6..0ed4bf6d8 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -45,6 +45,7 @@ #include "qsettingswrapper.h" #include "installercalculator.h" #include "uninstallercalculator.h" +#include "componentalias.h" #include "componentchecker.h" #include "globals.h" #include "binarycreator.h" @@ -56,11 +57,13 @@ #include "selfrestarter.h" #include "filedownloaderfactory.h" #include "updateoperationfactory.h" +#include "constants.h" #include <productkeycheck.h> #include <QSettings> #include <QtConcurrentRun> +#include <QtConcurrent> #include <QtCore/QCoreApplication> #include <QtCore/QDir> #include <QtCore/QDirIterator> @@ -120,56 +123,67 @@ static QStringList checkRunningProcessesFromList(const QStringList &processList) return stillRunningProcesses; } -static void deferredRename(const QString &oldName, const QString &newName, bool restart = false) +static bool filterMissingAliasesToInstall(const QString& component, const QList<ComponentAlias *> packages) { -#ifdef Q_OS_WIN - const QString currentExecutable = QCoreApplication::applicationFilePath(); - const QString tmpExecutable = generateTemporaryFileName(currentExecutable); - - QFile::rename(currentExecutable, tmpExecutable); - QFile::rename(oldName, newName); - - QStringList arguments; - if (restart) { - // Restart with same command line arguments as first executable - arguments = QCoreApplication::arguments(); - arguments.removeFirst(); // Remove program name - arguments.prepend(tmpExecutable); - arguments.prepend(QLatin1String("--") - + CommandLineOptions::scCleanupUpdate); - } else { - arguments.append(QLatin1String("--") - + CommandLineOptions::scCleanupUpdateOnly); - arguments.append(tmpExecutable); + bool packageFound = false; + for (qsizetype i = 0; i < packages.size(); ++i) { + packageFound = (packages.at(i)->name() == component); + if (packageFound) + break; } - QProcessWrapper::startDetached2(newName, arguments); + return !packageFound; +} -#else - QFile::remove(newName); - QFile::rename(oldName, newName); - SelfRestarter::setRestartOnQuit(restart); -#endif +static bool filterMissingPackagesToInstall(const QString& component, const PackagesList& packages) +{ + bool packageFound = false; + for (qsizetype i = 0; i < packages.size(); ++i) { + packageFound = (packages.at(i)->data(scName).toString() == component); + if (packageFound) + break; + } + return !packageFound; } +static QString getAppBundlePath() { + QString appDirPath = QCoreApplication::applicationDirPath(); + QDir dir(appDirPath); + while (!dir.isRoot()) { + if (dir.dirName().endsWith(QLatin1String(".app"))) + return dir.absolutePath(); + dir.cdUp(); + } + return QString(); +} // -- PackageManagerCorePrivate PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) : m_updateFinder(nullptr) + , m_aliasFinder(nullptr) , m_localPackageHub(std::make_shared<LocalPackageHub>()) , m_status(PackageManagerCore::Unfinished) , m_needsHardRestart(false) , m_testChecksum(false) , m_launchedAsRoot(AdminAuthorization::hasAdminRights()) + , m_commandLineInstance(false) + , m_defaultInstall(false) + , m_userSetBinaryMarker(false) + , m_checkAvailableSpace(true) , m_completeUninstall(false) , m_needToWriteMaintenanceTool(false) , m_dependsOnLocalInstallerBinary(false) + , m_autoAcceptLicenses(false) + , m_disableWriteMaintenanceTool(false) + , m_autoConfirmCommand(false) , m_core(core) , m_updates(false) + , m_aliases(false) , m_repoFetched(false) , m_updateSourcesAdded(false) , m_magicBinaryMarker(0) // initialize with pseudo marker , m_magicMarkerSupplement(BinaryContent::Default) + , m_foundEssentialUpdate(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) , m_installerCalculator(nullptr) @@ -177,37 +191,46 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) , m_proxyFactory(nullptr) , m_defaultModel(nullptr) , m_updaterModel(nullptr) + , m_componentSortFilterProxyModel(nullptr) , m_guiObject(nullptr) , m_remoteFileEngineHandler(nullptr) - , m_foundEssentialUpdate(false) - , m_commandLineInstance(false) - , m_defaultInstall(false) - , m_userSetBinaryMarker(false) - , m_checkAvailableSpace(true) - , m_autoAcceptLicenses(false) - , m_disableWriteMaintenanceTool(false) - , m_autoConfirmCommand(false) , m_datFileName(QString()) +#ifdef INSTALLCOMPRESSED + , m_allowCompressedRepositoryInstall(true) +#else + , m_allowCompressedRepositoryInstall(false) +#endif + , m_connectedOperations(0) { } PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, const QList<OperationBlob> &performedOperations, const QString &datFileName) : m_updateFinder(nullptr) + , m_aliasFinder(nullptr) , m_localPackageHub(std::make_shared<LocalPackageHub>()) , m_status(PackageManagerCore::Unfinished) , m_needsHardRestart(false) , m_testChecksum(false) , m_launchedAsRoot(AdminAuthorization::hasAdminRights()) + , m_commandLineInstance(false) + , m_defaultInstall(false) + , m_userSetBinaryMarker(false) + , m_checkAvailableSpace(true) , m_completeUninstall(false) , m_needToWriteMaintenanceTool(false) , m_dependsOnLocalInstallerBinary(false) + , m_autoAcceptLicenses(false) + , m_disableWriteMaintenanceTool(false) + , m_autoConfirmCommand(false) , m_core(core) , m_updates(false) + , m_aliases(false) , m_repoFetched(false) , m_updateSourcesAdded(false) , m_magicBinaryMarker(magicInstallerMaker) , m_magicMarkerSupplement(BinaryContent::Default) + , m_foundEssentialUpdate(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) , m_installerCalculator(nullptr) @@ -215,22 +238,21 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q , m_proxyFactory(nullptr) , m_defaultModel(nullptr) , m_updaterModel(nullptr) + , m_componentSortFilterProxyModel(nullptr) , m_guiObject(nullptr) , m_remoteFileEngineHandler(new RemoteFileEngineHandler) - , m_foundEssentialUpdate(false) - , m_commandLineInstance(false) - , m_defaultInstall(false) - , m_userSetBinaryMarker(false) - , m_checkAvailableSpace(true) - , m_autoAcceptLicenses(false) - , m_disableWriteMaintenanceTool(false) - , m_autoConfirmCommand(false) , m_datFileName(datFileName) +#ifdef INSTALLCOMPRESSED + , m_allowCompressedRepositoryInstall(true) +#else + , m_allowCompressedRepositoryInstall(false) +#endif + , m_connectedOperations(0) { foreach (const OperationBlob &operation, performedOperations) { - QScopedPointer<QInstaller::Operation> op(KDUpdater::UpdateOperationFactory::instance() + std::unique_ptr<QInstaller::Operation> op(KDUpdater::UpdateOperationFactory::instance() .create(operation.name, core)); - if (op.isNull()) { + if (!op) { qCWarning(QInstaller::lcInstallerInstallLog) << "Failed to load unknown operation" << operation.name; continue; @@ -241,7 +263,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q << operation.name; continue; } - m_performedOperationsOld.append(op.take()); + m_performedOperationsOld.append(op.release()); } connect(this, &PackageManagerCorePrivate::installationStarted, @@ -270,6 +292,7 @@ PackageManagerCorePrivate::~PackageManagerCorePrivate() qDeleteAll(m_performedOperationsCurrentSession); delete m_updateFinder; + delete m_aliasFinder; delete m_proxyFactory; delete m_defaultModel; @@ -430,6 +453,124 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c return true; } +bool PackageManagerCorePrivate::buildComponentAliases() +{ + // For now, aliases are only used for command line runs + if (!m_core->isCommandLineInstance()) + return true; + + { + const QList<ComponentAlias *> aliasList = componentAliases(); + if (aliasList.isEmpty()) + return true; + + for (const auto *alias : aliasList) { + // Create a new alias object for package manager core to take ownership of + ComponentAlias *newAlias = new ComponentAlias(m_core); + for (const QString &key : alias->keys()) + newAlias->setValue(key, alias->value(key)); + + m_componentAliases.insert(alias->name(), newAlias); + } + } + + if (m_core->isPackageViewer()) + return true; + // After aliases are loaded, perform sanity checks: + + // 1. Component check state is changed by alias selection, so store the initial state + storeCheckState(); + + QStringList aliasNamesSelectedForInstall; + + // 2. Get a list of alias names to be installed, dependency aliases needs to be listed first + // to get proper unstable state for parents + std::function<void(QStringList)> fetchAliases = [&](QStringList aliases) { + for (const QString &aliasName : aliases) { + ComponentAlias *alias = m_componentAliases.value(aliasName); + if (!alias || aliasNamesSelectedForInstall.contains(aliasName)) + continue; + if (!aliasNamesSelectedForInstall.contains(aliasName)) + aliasNamesSelectedForInstall.prepend(aliasName); + fetchAliases(QStringList() << QInstaller::splitStringWithComma(alias->value(scRequiredAliases)) + << QInstaller::splitStringWithComma(alias->value(scOptionalAliases))); + } + }; + for (const QString &installComponent : m_componentsToBeInstalled) { + ComponentAlias *alias = m_componentAliases.value(installComponent); + if (!alias) + continue; + if (!aliasNamesSelectedForInstall.contains(installComponent)) + aliasNamesSelectedForInstall.prepend(installComponent); + fetchAliases(QStringList() << QInstaller::splitStringWithComma(alias->value(scRequiredAliases)) + << QInstaller::splitStringWithComma(alias->value(scOptionalAliases))); + } + + Graph<QString> aliasGraph; + QList<ComponentAlias *> aliasesSelectedForInstall; + for (auto &aliasName : std::as_const(aliasNamesSelectedForInstall)) { + ComponentAlias *alias = m_componentAliases.value(aliasName); + if (!alias) + continue; + aliasGraph.addNode(alias->name()); + aliasGraph.addEdges(alias->name(), + QInstaller::splitStringWithComma(alias->value(scRequiredAliases)) << + QInstaller::splitStringWithComma(alias->value(scOptionalAliases))); + + if (!m_core->componentByName(alias->name())) { + // Name ok, select for sanity check calculation + alias->setSelected(true); + } else { + alias->setUnstable(ComponentAlias::ComponentNameConfict, + tr("Alias declares name that conflicts with an existing component \"%1\"") + .arg(alias->name())); + } + if (!aliasesSelectedForInstall.contains(alias)) + aliasesSelectedForInstall.append(alias); + } + + const QList<QString> sortedAliases = aliasGraph.sort(); + // 3. Check for cyclic dependency errors + if (aliasGraph.hasCycle()) { + setStatus(PackageManagerCore::Failure, installerCalculator()->error()); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), + tr("Unresolved component aliases"), + tr("Cyclic dependency between aliases \"%1\" and \"%2\" detected.") + .arg(aliasGraph.cycle().first, aliasGraph.cycle().second)); + + return false; + } + + // 4. Test for required aliases and components, this triggers setting the + // alias unstable in case of a broken reference. + for (const auto &aliasName : sortedAliases) { + ComponentAlias *alias = m_componentAliases.value(aliasName); + if (!alias) // sortedAliases may contain dependencies that don't exist, we don't know it yet + continue; + + alias->components(); + alias->aliases(); + } + + clearInstallerCalculator(); + // 5. Check for other errors preventing resolving components to install + if (!installerCalculator()->solve(aliasesSelectedForInstall)) { + setStatus(PackageManagerCore::Failure, installerCalculator()->error()); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), + tr("Unresolved component aliases"), installerCalculator()->error()); + + return false; + } + + for (auto *alias : std::as_const(m_componentAliases)) + alias->setSelected(false); + + // 6. Restore original state + restoreCheckState(); + + return true; +} + template <typename T> bool PackageManagerCorePrivate::loadComponentScripts(const T &components, const bool postScript) { @@ -468,6 +609,10 @@ void PackageManagerCorePrivate::cleanUpComponentEnvironment() // so we need to remove the current component script engine delete m_componentScriptEngine; m_componentScriptEngine = nullptr; + + // Calculators become invalid after clearing components + clearInstallerCalculator(); + clearUninstallerCalculator(); } ScriptEngine *PackageManagerCorePrivate::componentScriptEngine() const @@ -486,6 +631,9 @@ ScriptEngine *PackageManagerCorePrivate::controlScriptEngine() const void PackageManagerCorePrivate::clearAllComponentLists() { + qDeleteAll(m_componentAliases); + m_componentAliases.clear(); + QList<QInstaller::Component*> toDelete; toDelete << m_rootComponents << m_deletedReplacedComponents; @@ -645,6 +793,10 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> ¶ms if (isInstaller()) m_packageSources.insert(PackageSource(QUrl(QLatin1String("resource://metadata/")), 1)); + const QString aliasFilePath = m_core->settings().aliasDefinitionsFile(); + if (!aliasFilePath.isEmpty()) + m_aliasSources.insert(AliasSource(AliasSource::SourceFileFormat::Xml, aliasFilePath, -1)); + m_metadataJob.disconnect(); m_metadataJob.setAutoDelete(false); m_metadataJob.setPackageManagerCore(m_core); @@ -773,7 +925,7 @@ QString PackageManagerCorePrivate::maintenanceToolAliasPath() const if (aliasName.isEmpty()) return QString(); - const bool isRoot = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()); + const bool isRoot = m_core->hasAdminRights(); const QString applicationsDir = m_core->value( isRoot ? QLatin1String("ApplicationsDir") : QLatin1String("ApplicationsDirUser") ); @@ -864,7 +1016,7 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() if (key == scRunProgramDescription || key == scRunProgram || key == scRunProgramArguments) continue; QVariant value = m_data.value(key); - if (value.canConvert(QVariant::String)) + if (value.canConvert<QString>()) value = replacePath(value.toString(), targetDir(), QLatin1String(scRelocatable)); variables.insert(key, value); } @@ -888,8 +1040,8 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() QFile file(targetDir() + QLatin1Char('/') + QLatin1String("network.xml")); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QXmlStreamWriter writer(&file); - writer.setCodec("UTF-8"); + QString outputStr; + QXmlStreamWriter writer(&outputStr); writer.setAutoFormatting(true); writer.writeStartDocument(); @@ -922,6 +1074,8 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() writer.writeEndElement(); writer.writeTextElement(QLatin1String("LocalCachePath"), m_data.settings().localCachePath()); writer.writeEndElement(); + + file.write(outputStr.toUtf8()); } setDefaultFilePermissions(&file, DefaultFilePermissions::NonExecutable); } @@ -1070,8 +1224,11 @@ void PackageManagerCorePrivate::connectOperationToInstaller(Operation *const ope connect(m_core, SIGNAL(installationInterrupted()), operationObject, SLOT(cancelOperation())); if (mo->indexOfSignal(QMetaObject::normalizedSignature("progressChanged(double)")) > -1) { + // create unique object names for progress information track + operationObject->setObjectName(QLatin1String("operation_%1").arg(QString::number(m_connectedOperations))); ProgressCoordinator::instance()->registerPartProgress(operationObject, SIGNAL(progressChanged(double)), operationPartSize); + m_connectedOperations++; } } } @@ -1296,7 +1453,7 @@ void PackageManagerCorePrivate::writeMaintenanceToolAppBundle(OperationList &per const QString after = QLatin1String("<string>") + QFileInfo(maintenanceToolName()).baseName() + QLatin1String("</string>"); while (!in.atEnd()) - out << in.readLine().replace(before, after) << endl; + out << in.readLine().replace(before, after) << Qt::endl; // copy qt_menu.nib if it exists op = createOwnedOperation(QLatin1String("Mkdir")); @@ -1390,6 +1547,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper bool newBinaryWritten = false; QString mtName = maintenanceToolName(); const QString installerBaseBinary = replaceVariables(m_installerBaseBinaryUnreplaced); + bool macOsMTBundleExtracted = false; if (!installerBaseBinary.isEmpty() && QFileInfo::exists(installerBaseBinary)) { qCDebug(QInstaller::lcInstallerInstallLog) << "Got a replacement installer base binary:" << installerBaseBinary; @@ -1397,25 +1555,9 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper // In macOS the installerbase is a whole app bundle. We do not modify the maintenancetool name in app bundle // so that possible signing and notarization will remain. Therefore, the actual maintenance tool name might // differ from the one defined in the settings. - try { - const QString maintenanceToolRenamedName = installerBaseBinary + QLatin1String(".new"); - qCDebug(QInstaller::lcInstallerInstallLog) << "Writing maintenance tool " << maintenanceToolRenamedName; - QInstaller::copyDirectoryContents(installerBaseBinary, maintenanceToolRenamedName); - - newBinaryWritten = true; - mtName = installerBaseBinary; - } catch (const Error &error) { - qCWarning(QInstaller::lcInstallerInstallLog) << error.message(); - } - try { - QInstaller::removeDirectory(installerBaseBinary); - qCDebug(QInstaller::lcInstallerInstallLog) << "Removed installer base binary" - << installerBaseBinary << "after updating the maintenance tool."; - } catch (const Error &error) { - qCDebug(QInstaller::lcInstallerInstallLog) << "Cannot remove installer base binary" - << installerBaseBinary << "after updating the maintenance tool:" - << error.message(); - } + newBinaryWritten = true; + mtName = installerBaseBinary; + macOsMTBundleExtracted = true; } else { writeMaintenanceToolAppBundle(performedOperations); QFile replacementBinary(installerBaseBinary); @@ -1557,7 +1699,9 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper << (restart ? "true." : "false."); if (newBinaryWritten) { - if (isInstaller()) + if (macOsMTBundleExtracted) + deferredRename(mtName, targetDir() + QDir::separator() + fi.fileName(), restart); + else if (isInstaller()) QFile::rename(mtName + QLatin1String(".new"), mtName); else deferredRename(mtName + QLatin1String(".new"), mtName, restart); @@ -1740,7 +1884,7 @@ bool PackageManagerCorePrivate::runInstaller() throw Error(tr("It is not possible to install from network location")); } - if (!adminRightsGained) { + if (!m_core->hasAdminRights()) { foreach (Component *component, componentsToInstall) { if (component->value(scRequiresAdminRights, scFalse) == scFalse) continue; @@ -1774,7 +1918,7 @@ bool PackageManagerCorePrivate::runInstaller() double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; // Now install the requested components - unpackAndInstallComponents(componentsToInstall, progressOperationSize, adminRightsGained); + unpackAndInstallComponents(componentsToInstall, progressOperationSize); if (m_core->isOfflineOnly() && PackageManagerCore::createLocalRepositoryFromBinary()) { emit m_core->titleMessageChanged(tr("Creating local repository")); @@ -1972,7 +2116,7 @@ bool PackageManagerCorePrivate::runPackageUpdater() } // we did not request admin rights till we found out that a component/ undo needs admin rights - if (updateAdminRights && !adminRightsGained) { + if (updateAdminRights && !m_core->hasAdminRights()) { m_core->gainAdminRights(); m_core->dropAdminRights(); } @@ -1993,7 +2137,7 @@ bool PackageManagerCorePrivate::runPackageUpdater() if (undoOperations.count() > 0) { ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Removing deselected components...")); - runUndoOperations(undoOperations, undoOperationProgressSize, adminRightsGained, true); + runUndoOperations(undoOperations, undoOperationProgressSize, true); } m_performedOperationsOld = nonRevertedOperations; // these are all operations left: those not reverted @@ -2001,7 +2145,7 @@ bool PackageManagerCorePrivate::runPackageUpdater() const double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; // Now install the requested new components - unpackAndInstallComponents(componentsToInstall, progressOperationSize, adminRightsGained); + unpackAndInstallComponents(componentsToInstall, progressOperationSize); emit m_core->titleMessageChanged(tr("Creating Maintenance Tool")); @@ -2070,7 +2214,7 @@ bool PackageManagerCorePrivate::runUninstaller() } // We did not yet request elevated permissions but they are required. - if (updateAdminRights && !adminRightsGained) { + if (updateAdminRights && !m_core->hasAdminRights()) { m_core->gainAdminRights(); m_core->dropAdminRights(); } @@ -2078,7 +2222,7 @@ bool PackageManagerCorePrivate::runUninstaller() const int uninstallOperationCount = countProgressOperations(undoOperations); const double undoOperationProgressSize = double(1) / double(uninstallOperationCount); - runUndoOperations(undoOperations, undoOperationProgressSize, adminRightsGained, false); + runUndoOperations(undoOperations, undoOperationProgressSize, false); // No operation delete here, as all old undo operations are deleted in the destructor. deleteMaintenanceTool(); // this will also delete the TargetDir on Windows @@ -2087,7 +2231,7 @@ bool PackageManagerCorePrivate::runUninstaller() // If not on Windows, we need to remove TargetDir manually. #ifndef Q_OS_WIN if (QVariant(m_core->value(scRemoveTargetDir)).toBool() && !targetDir().isEmpty()) { - if (updateAdminRights && !adminRightsGained) + if (updateAdminRights && !m_core->hasAdminRights()) adminRightsGained = m_core->gainAdminRights(); removeDirectoryThreaded(targetDir(), true); qCDebug(QInstaller::lcInstallerInstallLog) << "Complete uninstallation was chosen."; @@ -2249,7 +2393,7 @@ bool PackageManagerCorePrivate::runOfflineGenerator() } void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &components, - double progressOperationSize, bool adminRightsGained) + double progressOperationSize) { OperationList unpackOperations; bool becameAdmin = false; @@ -2273,7 +2417,7 @@ void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &compo // There's currently no way to control this on a per-operation basis, so // any op requesting execution as admin means all extracts are done as admin. - if (!adminRightsGained && !becameAdmin && op->value(QLatin1String("admin")).toBool()) + if (!m_core->hasAdminRights() && op->value(QLatin1String("admin")).toBool()) becameAdmin = m_core->gainAdminRights(); } } @@ -2321,7 +2465,7 @@ void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &compo continue; } // Backup may request performing operation as admin - if (!adminRightsGained && !becameAdmin && operation->value(QLatin1String("admin")).toBool()) + if (!m_core->hasAdminRights() && operation->value(QLatin1String("admin")).toBool()) becameAdmin = m_core->gainAdminRights(); } @@ -2390,8 +2534,7 @@ void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &compo ProgressCoordinator::instance()->emitDetailTextChanged(tr("Done")); } -void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize, - bool adminRightsGained) +void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize) { OperationList operations = component->operations(Operation::Install); if (!component->operationsCreatedSuccessfully()) @@ -2412,7 +2555,7 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr // maybe this operations wants us to be admin... bool becameAdmin = false; - if (!adminRightsGained && operation->value(QLatin1String("admin")).toBool()) { + if (!m_core->hasAdminRights() && operation->value(QLatin1String("admin")).toBool()) { becameAdmin = m_core->gainAdminRights(); qCDebug(QInstaller::lcInstallerInstallLog) << operation->name() << "as admin:" << becameAdmin; } @@ -2501,23 +2644,67 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr ProgressCoordinator::instance()->emitDetailTextChanged(tr("Done")); } -bool PackageManagerCorePrivate::runningProcessesFound() +PackageManagerCore::Status PackageManagerCorePrivate::fetchComponentsAndInstall(const QStringList& components) { - //Check if there are processes running in the install - QStringList excludeFiles = m_allowedRunningProcesses; - excludeFiles.append(maintenanceToolName()); + // init default model before fetching remote packages tree + ComponentModel *model = m_core->defaultComponentModel(); + Q_UNUSED(model); - const QString performModeWarning = m_completeUninstall - ? QLatin1String("Unable to remove components.") - : QLatin1String("Unable to update components."); + bool fallbackReposFetched = false; + auto fetchComponents = [&]() { + bool packagesFound = m_core->fetchPackagesWithFallbackRepositories(components, fallbackReposFetched); - QStringList runningProcesses = runningInstallerProcesses(excludeFiles); - if (!runningProcesses.isEmpty()) { - qCWarning(QInstaller::lcInstallerInstallLog).noquote().nospace() << performModeWarning - << " Please stop these processes: " << runningProcesses << " and try again."; + if (!packagesFound) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() + << "No components available with the current selection."; + setStatus(PackageManagerCore::Canceled); + return false; + } + QString errorMessage; + bool unstableAliasFound = false; + if (m_core->checkComponentsForInstallation(components, errorMessage, unstableAliasFound, fallbackReposFetched)) { + if (!errorMessage.isEmpty()) + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage; + if (calculateComponentsAndRun()) { + if (m_core->isOfflineGenerator()) + qCDebug(QInstaller::lcInstallerInstallLog) << "Created installer to:" << offlineBinaryName(); + else + qCDebug(QInstaller::lcInstallerInstallLog) << "Components installed successfully"; + } + } else { + // We found unstable alias and all repos were not fetched. Alias might have dependency to component + // which exists in non-default repository. Fetch all repositories now. + if (unstableAliasFound && !fallbackReposFetched) { + return false; + } else { + for (const QString &possibleAliasName : components) { + if (ComponentAlias *alias = m_core->aliasByName(possibleAliasName)) { + if (alias->componentErrorMessage().isEmpty()) + continue; + qCWarning(QInstaller::lcInstallerInstallLog).noquote().nospace() << alias->componentErrorMessage(); + } + } + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage + << "No components available with the current selection."; + } + } return true; + }; + + if (!fetchComponents() && !fallbackReposFetched) { + fallbackReposFetched = true; + setStatus(PackageManagerCore::Running); + qCDebug(QInstaller::lcInstallerInstallLog).noquote() + << "Components not found with the current selection." + << "Searching from additional repositories"; + if (!ProductKeyCheck::instance()->securityWarning().isEmpty()) { + qCWarning(QInstaller::lcInstallerInstallLog) << ProductKeyCheck::instance()->securityWarning(); + } + enableAllCategories(); + fetchComponents(); } - return false; + + return m_core->status(); } void PackageManagerCorePrivate::setComponentSelection(const QString &id, Qt::CheckState state) @@ -2637,9 +2824,12 @@ void PackageManagerCorePrivate::registerMaintenanceTool() settings.setValue(QLatin1String("Comments"), m_data.value(scTitle)); settings.setValue(QLatin1String("InstallDate"), QDateTime::currentDateTime().toString()); settings.setValue(QLatin1String("InstallLocation"), QDir::toNativeSeparators(targetDir())); - settings.setValue(QLatin1String("UninstallString"), quoted(maintenanceTool)); - settings.setValue(QLatin1String("ModifyPath"), QString(quoted(maintenanceTool) - + QLatin1String(" --manage-packages"))); + settings.setValue(QLatin1String("UninstallString"), QString(quoted(maintenanceTool) + + QLatin1String(" --") + CommandLineOptions::scStartUninstallerLong)); + if (!isOfflineOnly()) { + settings.setValue(QLatin1String("ModifyPath"), QString(quoted(maintenanceTool) + + QLatin1String(" --") + CommandLineOptions::scStartPackageManagerLong)); + } // required disk space of the installed components quint64 estimatedSizeKB = m_core->requiredDiskSpace() / 1024; // add required space for the maintenance tool @@ -2676,8 +2866,8 @@ void PackageManagerCorePrivate::unregisterMaintenanceTool() #endif } -void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOperations, double progressSize, - bool adminRightsGained, bool deleteOperation) +void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOperations, + double progressSize, bool deleteOperation) { try { const int operationsCount = undoOperations.size(); @@ -2688,7 +2878,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera throw Error(tr("Installation canceled by user")); bool becameAdmin = false; - if (!adminRightsGained && undoOperation->value(QLatin1String("admin")).toBool()) + if (!m_core->hasAdminRights() && undoOperation->value(QLatin1String("admin")).toBool()) becameAdmin = m_core->gainAdminRights(); connectOperationToInstaller(undoOperation, progressSize); @@ -2796,6 +2986,25 @@ LocalPackagesMap PackageManagerCorePrivate::localInstalledPackages() return m_localPackageHub->localPackages(); } +QList<ComponentAlias *> PackageManagerCorePrivate::componentAliases() +{ + if (m_aliases && m_aliasFinder) + return m_aliasFinder->aliases(); + + m_aliases = false; + delete m_aliasFinder; + + m_aliasFinder = new AliasFinder(m_core); + m_aliasFinder->setAliasSources(m_aliasSources); + if (!m_aliasFinder->run()) { + qCDebug(lcDeveloperBuild) << "No component aliases found." << Qt::endl; + return QList<ComponentAlias *>(); + } + + m_aliases = true; + return m_aliasFinder->aliases(); +} + bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories(DownloadType type) { m_updates = false; @@ -2858,9 +3067,9 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool compress continue; if (data->repository().isCompressed()) - m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 2)); + m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 2, data->repository().postLoadComponentScript())); else - m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 0)); + m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 0, data->repository().postLoadComponentScript())); ProductKeyCheck::instance()->addPackagesFromXml(data->path() + QLatin1String("/Updates.xml")); } @@ -2913,6 +3122,125 @@ void PackageManagerCorePrivate::updateComponentInstallActions() component->setInstallAction(ComponentModelHelper::Install); } +bool PackageManagerCorePrivate::enableAllCategories() +{ + QSet<RepositoryCategory> repoCategories = m_data.settings().repositoryCategories(); + bool additionalRepositoriesEnabled = false; + for (const auto &category : repoCategories) { + if (!category.isEnabled()) { + additionalRepositoriesEnabled = true; + enableRepositoryCategory(category, true); + } + } + return additionalRepositoriesEnabled; +} + +void PackageManagerCorePrivate::enableRepositoryCategory(const RepositoryCategory &repoCategory, const bool enable) +{ + RepositoryCategory replacement = repoCategory; + replacement.setEnabled(enable); + QSet<RepositoryCategory> tmpRepoCategories = m_data.settings().repositoryCategories(); + if (tmpRepoCategories.contains(repoCategory)) { + tmpRepoCategories.remove(repoCategory); + tmpRepoCategories.insert(replacement); + m_data.settings().addRepositoryCategories(tmpRepoCategories); + } +} + +bool PackageManagerCorePrivate::installablePackagesFound(const QStringList& components) +{ + if (components.isEmpty()) + return true; + + PackagesList remotes = remotePackages(); + + auto componentsNotFoundForInstall = QtConcurrent::blockingFiltered( + components, + [remotes](const QString& installerPackage) { + return filterMissingPackagesToInstall(installerPackage, remotes); + } + ); + + if (componentsNotFoundForInstall.count() > 0) { + QList<ComponentAlias *> aliasList = componentAliases(); + auto aliasesNotFoundForInstall = QtConcurrent::blockingFiltered( + componentsNotFoundForInstall, + [aliasList](const QString& installerPackage) { + return filterMissingAliasesToInstall(installerPackage, aliasList); + } + ); + + if (aliasesNotFoundForInstall.count() > 0) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot select " << aliasesNotFoundForInstall.join(QLatin1String(", ")) << ". Component(s) not found."; + setStatus(PackageManagerCore::NoPackagesFound); + return false; + } + } + return true; +} + +void PackageManagerCorePrivate::deferredRename(const QString &oldName, const QString &newName, bool restart) +{ + +#ifdef Q_OS_WINDOWS + const QString currentExecutable = QCoreApplication::applicationFilePath(); + QString tmpExecutable = generateTemporaryFileName(currentExecutable); + QFile::rename(currentExecutable, tmpExecutable); + QFile::rename(oldName, newName); + + QStringList arguments; + if (restart) { + // Restart with same command line arguments as first executable + arguments = QCoreApplication::arguments(); + arguments.removeFirst(); // Remove program name + arguments.prepend(tmpExecutable); + arguments.prepend(QLatin1String("--") + + CommandLineOptions::scCleanupUpdate); + } else { + arguments.append(QLatin1String("--") + + CommandLineOptions::scCleanupUpdateOnly); + arguments.append(tmpExecutable); + } + QProcessWrapper::startDetached2(newName, arguments); +#elif defined Q_OS_MACOS + // In macos oldName is the name of the maintenancetool we got from repository + // It might be extracted to a folder to avoid overlapping with running maintenancetool + // Here, ditto renames it to newName (and possibly moves from the subfolder). + if (oldName != newName) { + //1. Rename/move maintenancetool + QProcessWrapper process; + process.start(QLatin1String("ditto"), QStringList() << oldName << newName); + if (!process.waitForFinished()) { + qCDebug(QInstaller::lcInstallerInstallLog) << "Failed to rename maintenancetool from :" << oldName << " to "<<newName; + } + //2. Remove subfolder + QDir subDirectory(oldName); + subDirectory.cdUp(); + QString subDirectoryPath = QDir::cleanPath(subDirectory.absolutePath()); + QString targetDirectoryPath = QDir::cleanPath(targetDir()); + + //Make sure there is subdirectory in the targetdir so we don't delete the installation + if (subDirectoryPath.startsWith(targetDirectoryPath) && subDirectoryPath != targetDirectoryPath) + subDirectory.removeRecursively(); + } + + //3. If new maintenancetool name differs from original, remove the original maintenance tool + if (!isInstaller()) { + QString currentAppBundlePath = getAppBundlePath(); + if (currentAppBundlePath != newName) { + QDir oldBundlePath(currentAppBundlePath); + oldBundlePath.removeRecursively(); + } + } + SelfRestarter::setRestartOnQuit(restart); + +#elif defined Q_OS_LINUX + QFile::remove(newName); + QFile::rename(oldName, newName); + SelfRestarter::setRestartOnQuit(restart); +#endif +} + void PackageManagerCorePrivate::connectOperationCallMethodRequest(Operation *const operation) { QObject *const operationObject = dynamic_cast<QObject *> (operation); @@ -2973,16 +3301,16 @@ void PackageManagerCorePrivate::addPathForDeletion(const QString &path) } void PackageManagerCorePrivate::unpackAndInstallComponents(const QList<Component *> &components, - const double progressOperationSize, const bool adminRightsGained) + const double progressOperationSize) { // Perform extract operations - unpackComponents(components, progressOperationSize, adminRightsGained); + unpackComponents(components, progressOperationSize); // Perform rest of the operations and mark component as installed const int componentsToInstallCount = components.size(); int installedComponents = 0; foreach (Component *component, components) { - installComponent(component, progressOperationSize, adminRightsGained); + installComponent(component, progressOperationSize); ++installedComponents; ProgressCoordinator::instance()->emitAdditionalProgressStatus(tr("%1 of %2 components installed.") @@ -3008,24 +3336,6 @@ void PackageManagerCorePrivate::processFilesForDelayedDeletion() } } -void PackageManagerCorePrivate::findExecutablesRecursive(const QString &path, const QStringList &excludeFiles, QStringList *result) -{ - QDirIterator it(path, QDir::NoDotAndDotDot | QDir::Executable | QDir::Files | QDir::System, QDirIterator::Subdirectories ); - - while (it.hasNext()) - result->append(QDir::toNativeSeparators(it.next().toLower())); - - foreach (const QString &process, excludeFiles) - result->removeAll(QDir::toNativeSeparators(process.toLower())); -} - -QStringList PackageManagerCorePrivate::runningInstallerProcesses(const QStringList &excludeFiles) -{ - QStringList resultFiles; - findExecutablesRecursive(QCoreApplication::applicationDirPath(), excludeFiles, &resultFiles); - return checkRunningProcessesFromList(resultFiles); -} - bool PackageManagerCorePrivate::calculateComponentsAndRun() { bool componentsOk = m_core->recalculateAllComponents(); @@ -3042,9 +3352,8 @@ bool PackageManagerCorePrivate::calculateComponentsAndRun() qCDebug(QInstaller::lcInstallerInstallLog).noquote() << htmlToString(m_core->componentResolveReasons()); - QString spaceInfo; - const bool spaceOk = m_core->checkAvailableSpace(spaceInfo); - qCDebug(QInstaller::lcInstallerInstallLog) << spaceInfo; + const bool spaceOk = m_core->checkAvailableSpace(); + qCDebug(QInstaller::lcInstallerInstallLog) << m_core->availableSpaceMessage(); if (!spaceOk || !(m_autoConfirmCommand || askUserConfirmCommand())) { qCDebug(QInstaller::lcInstallerInstallLog) << "Installation aborted."; @@ -3071,6 +3380,13 @@ bool PackageManagerCorePrivate::acceptLicenseAgreements() const m_core->addLicenseItem(component->licenses()); } + const QString acceptanceText = ProductKeyCheck::instance()->licenseAcceptanceText(); + if (!acceptanceText.isEmpty()) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote() << acceptanceText; + if (!m_autoAcceptLicenses && !acceptRejectCliQuery()) + return false; + } + QHash<QString, QMap<QString, QString>> priorityHash = m_core->sortedLicenses(); QStringList priorities = priorityHash.keys(); priorities.sort(); @@ -3119,6 +3435,23 @@ bool PackageManagerCorePrivate::askUserAcceptLicense(const QString &name, const } } +bool PackageManagerCorePrivate::acceptRejectCliQuery() const +{ + forever { + const QString input = m_core->readConsoleLine(QLatin1String("Accept|Reject")); + + if (QString::compare(input, QLatin1String("Accept"), Qt::CaseInsensitive) == 0 + || QString::compare(input, QLatin1String("A"), Qt::CaseInsensitive) == 0) { + return true; + } else if (QString::compare(input, QLatin1String("Reject"), Qt::CaseInsensitive) == 0 + || QString::compare(input, QLatin1String("R"), Qt::CaseInsensitive) == 0) { + return false; + } else { + qCDebug(QInstaller::lcInstallerInstallLog) << "Unknown answer:" << input; + } + } +} + bool PackageManagerCorePrivate::askUserConfirmCommand() const { qCDebug(QInstaller::lcInstallerInstallLog) << "Do you want to continue?"; diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index 74cb0667c..c1b50e615 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -54,11 +54,15 @@ using namespace KDUpdater; namespace QInstaller { struct BinaryLayout; +struct AliasSource; +class AliasFinder; class ScriptEngine; class ComponentModel; +class ComponentAlias; class InstallerCalculator; class UninstallerCalculator; class RemoteFileEngineHandler; +class ComponentSortFilterProxyModel; class PackageManagerCorePrivate : public QObject { @@ -106,6 +110,7 @@ public: QString configurationFileName() const; bool buildComponentTree(QHash<QString, Component*> &components, bool loadScript); + bool buildComponentAliases(); template <typename T> bool loadComponentScripts(const T &components, const bool postScript = false); @@ -169,13 +174,11 @@ public: m_performedOperationsCurrentSession.clear(); } - void unpackComponents(const QList<Component *> &components, double progressOperationSize, - bool adminRightsGained = false); + void unpackComponents(const QList<Component *> &components, double progressOperationSize); - void installComponent(Component *component, double progressOperationSize, - bool adminRightsGained = false); + void installComponent(Component *component, double progressOperationSize); + PackageManagerCore::Status fetchComponentsAndInstall(const QStringList& components); - bool runningProcessesFound(); void setComponentSelection(const QString &id, Qt::CheckState state); signals: @@ -188,8 +191,10 @@ signals: public: UpdateFinder *m_updateFinder; + AliasFinder *m_aliasFinder; QSet<PackageSource> m_packageSources; QSet<PackageSource> m_compressedPackageSources; + QSet<AliasSource> m_aliasSources; std::shared_ptr<LocalPackageHub> m_localPackageHub; QStringList m_filesForDelayedDeletion; @@ -217,6 +222,8 @@ public: QList<QInstaller::Component*> m_updaterComponentsDeps; QList<QInstaller::Component*> m_updaterDependencyReplacements; + QHash<QString, QInstaller::ComponentAlias *> m_componentAliases; + OperationList m_ownedOperations; OperationList m_performedOperationsOld; OperationList m_performedOperationsCurrentSession; @@ -244,7 +251,7 @@ private slots: private: void unpackAndInstallComponents(const QList<Component *> &components, - const double progressOperationSize, const bool adminRightsGained); + const double progressOperationSize); void deleteMaintenanceTool(); void deleteMaintenanceToolAlias(); @@ -257,18 +264,19 @@ private: void writeMaintenanceToolAppBundle(OperationList &performedOperations); void runUndoOperations(const OperationList &undoOperations, double undoOperationProgressSize, - bool adminRightsGained, bool deleteOperation); + bool deleteOperation); PackagesList remotePackages(); LocalPackagesMap localInstalledPackages(); + QList<ComponentAlias *> componentAliases(); + bool fetchMetaInformationFromRepositories(DownloadType type = DownloadType::All); bool addUpdateResourcesFromRepositories(bool compressedRepository = false); void processFilesForDelayedDeletion(); - void findExecutablesRecursive(const QString &path, const QStringList &excludeFiles, QStringList *result); - QStringList runningInstallerProcesses(const QStringList &exludeFiles); bool calculateComponentsAndRun(); bool acceptLicenseAgreements() const; bool askUserAcceptLicense(const QString &name, const QString &content) const; + bool acceptRejectCliQuery() const; bool askUserConfirmCommand() const; bool packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const; void commitPendingUnstableComponents(); @@ -276,6 +284,13 @@ private: void createLocalDependencyHash(const QString &componentName, const QString &dependencies); void updateComponentInstallActions(); + bool enableAllCategories(); + void enableRepositoryCategory(const RepositoryCategory &repoCategory, const bool enable); + + bool installablePackagesFound(const QStringList& components); + + void deferredRename(const QString &oldName, const QString &newName, bool restart = false); + // remove once we deprecate isSelected, setSelected etc... void restoreCheckState(); void storeCheckState(); @@ -286,6 +301,7 @@ private: TempPathDeleter m_tmpPathDeleter; bool m_updates; + bool m_aliases; bool m_repoFetched; bool m_updateSourcesAdded; qint64 m_magicBinaryMarker; @@ -308,6 +324,7 @@ private: ComponentModel *m_defaultModel; ComponentModel *m_updaterModel; + ComponentSortFilterProxyModel *m_componentSortFilterProxyModel; QObject *m_guiObject; QScopedPointer<RemoteFileEngineHandler> m_remoteFileEngineHandler; @@ -325,6 +342,9 @@ private: QHash<QString, QStringList > m_componentReplaces; QString m_datFileName; + bool m_allowCompressedRepositoryInstall; + int m_connectedOperations; + QStringList m_componentsToBeInstalled; }; } // namespace QInstaller diff --git a/src/libs/installer/packagemanagercoredata.cpp b/src/libs/installer/packagemanagercoredata.cpp index 1d5b9f713..1113908bd 100644 --- a/src/libs/installer/packagemanagercoredata.cpp +++ b/src/libs/installer/packagemanagercoredata.cpp @@ -36,6 +36,7 @@ #include <QDir> #include <QRegularExpression> #include <QSettings> +#include <QStandardPaths> #ifdef Q_OS_WIN # include <windows.h> @@ -95,7 +96,7 @@ PackageManagerCoreData::PackageManagerCoreData(const QHash<QString, QString> &va if (isInstaller) { addNewVariable(scTargetDir, replaceVariables(m_settings.targetDir())); addNewVariable(scTargetConfigurationFile, m_settings.configurationFileName()); - addNewVariable(scStartMenuDir, m_settings.startMenuDir()); + addNewVariable(scStartMenuDir, replaceVariables(m_settings.startMenuDir())); } else { #ifdef Q_OS_MACOS addNewVariable(scTargetDir, QFileInfo(QCoreApplication::applicationDirPath() + QLatin1String("/../../..")).absoluteFilePath()); @@ -257,6 +258,8 @@ QVariant PackageManagerCoreData::value(const QString &key, const QVariant &_defa if (!filename.isEmpty() && !regKey.isEmpty() && registry.contains(regKey)) return registry.value(regKey).toString(); } +#else + Q_UNUSED(format) #endif if (m_variables.contains(key)) diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp index c3642e4ee..1f0462eea 100644 --- a/src/libs/installer/packagemanagergui.cpp +++ b/src/libs/installer/packagemanagergui.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -412,10 +412,13 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent) connect(this, &QDialog::rejected, m_core, &PackageManagerCore::setCanceled); connect(this, &PackageManagerGui::interrupted, m_core, &PackageManagerCore::interrupt); - // both queued to show the finished page once everything is done + // all queued to show the finished page once everything is done connect(m_core, &PackageManagerCore::installationFinished, this, &PackageManagerGui::showFinishedPage, Qt::QueuedConnection); + connect(m_core, &PackageManagerCore::offlineGenerationFinished, + this, &PackageManagerGui::showFinishedPage, + Qt::QueuedConnection); connect(m_core, &PackageManagerCore::uninstallationFinished, this, &PackageManagerGui::showFinishedPage, Qt::QueuedConnection); @@ -689,6 +692,22 @@ bool PackageManagerGui::isButtonEnabled(int wb) } /*! + Sets \a buttonText for button specified by \a buttonId to a installer page \a pageId. + + \note In some pages, installer will change the button text when entering + the page. In that case, you need to connect to \c entered() -signal of the + page to change the \a buttonText. + + \sa {gui::setWizardPageButtonText}{gui.setWizardPageButtonText} +*/ +void PackageManagerGui::setWizardPageButtonText(int pageId, int buttonId, const QString &buttonText) +{ + PackageManagerPage *const p = qobject_cast<PackageManagerPage*> (page(pageId)); + if (p) + p->setButtonText(static_cast<QWizard::WizardButton>(buttonId), buttonText); +} + +/*! Sets a validator for the custom page specified by \a name and \a callbackName requested by \a component. */ @@ -1245,10 +1264,10 @@ void PackageManagerGui::currentPageChanged(int newId) PackageManagerPage::PackageManagerPage(PackageManagerCore *core) : m_complete(true) , m_titleColor(QString()) + , m_showOnPageList(true) , m_needsSettingsButton(false) , m_core(core) , validatorComponent(nullptr) - , m_showOnPageList(true) { if (!m_core->settings().titleColor().isEmpty()) m_titleColor = m_core->settings().titleColor(); @@ -1452,19 +1471,12 @@ int PackageManagerPage::nextId() const if (next == PackageManagerCore::LicenseCheck) { // calculate the page after the license page const int nextNextId = gui()->pageIds().value(gui()->pageIds().indexOf(next) + 1, -1); - const PackageManagerCore *const core = packageManagerCore(); + PackageManagerCore *const core = packageManagerCore(); if (core->isUninstaller()) return nextNextId; // forcibly hide the license page if we run as uninstaller - - foreach (Component* component, core->orderedComponentsToInstall()) { - if (core->isMaintainer() && component->isInstalled()) - continue; // package manager or updater, hide as long as the component is installed - - // The component is about to be installed and provides a license, so the page needs to - // be shown. - if (!component->licenses().isEmpty()) - return next; - } + core->recalculateAllComponents(); + if (core->hasLicenses()) + return next; return nextNextId; // no component with a license or all components with license installed } return next; // default, show the next page @@ -1503,7 +1515,7 @@ IntroductionPage::IntroductionPage(PackageManagerCore *core) , m_removeAllComponents(nullptr) { setObjectName(QLatin1String("IntroductionPage")); - setColoredTitle(tr("Setup - %1").arg(productName())); + setColoredTitle(tr("Welcome")); QVBoxLayout *layout = new QVBoxLayout(this); setLayout(layout); @@ -1551,6 +1563,8 @@ IntroductionPage::IntroductionPage(PackageManagerCore *core) m_errorLabel = new QLabel(this); m_errorLabel->setWordWrap(true); + m_errorLabel->setTextFormat(Qt::RichText); + m_errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); boxLayout->addWidget(m_errorLabel); m_errorLabel->setObjectName(QLatin1String("ErrorLabel")); @@ -1607,7 +1621,7 @@ bool IntroductionPage::validatePage() bool isOfflineOnlyInstaller = core->isInstaller() && core->isOfflineOnly(); // If not offline only installer, at least one valid repository needs to be available - if (!isOfflineOnlyInstaller && !validRepositoriesAvailable()) { + if (!isOfflineOnlyInstaller && !core->validRepositoriesAvailable()) { setErrorMessage(QLatin1String("<font color=\"red\">") + tr("At least one valid and enabled " "repository required for this action to succeed.") + QLatin1String("</font>")); return isComplete(); @@ -1654,21 +1668,12 @@ bool IntroductionPage::validatePage() // fetch common packages if (core->isInstaller() || core->isPackageManager()) { - bool localPackagesTreeFetched = false; if (!m_allPackagesFetched) { // first try to fetch the server side packages tree m_allPackagesFetched = core->fetchRemotePackagesTree(); if (!m_allPackagesFetched) { QString error = core->error(); - if (core->isPackageManager() && core->status() != PackageManagerCore::ForceUpdate) { - // if that fails and we're in maintenance mode, try to fetch local installed tree - localPackagesTreeFetched = core->fetchLocalPackagesTree(); - if (localPackagesTreeFetched) { - // if that succeeded, adjust error message - error = QLatin1String("<font color=\"red\">") + error + tr(" Only local package " - "management available.") + QLatin1String("</font>"); - } - } else if (core->status() == PackageManagerCore::ForceUpdate) { + 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('&'))); @@ -1686,7 +1691,7 @@ bool IntroductionPage::validatePage() } } - if (m_allPackagesFetched || localPackagesTreeFetched) + if (m_allPackagesFetched) setComplete(true); } @@ -1833,22 +1838,6 @@ void IntroductionPage::setErrorMessage(const QString &error) #endif } -/*! - Returns \c true if at least one valid and enabled repository is available. -*/ -bool IntroductionPage::validRepositoriesAvailable() const -{ - const PackageManagerCore *const core = packageManagerCore(); - bool valid = false; - - foreach (const Repository &repo, core->settings().repositories()) { - if (repo.isEnabled() && repo.isValid()) { - valid = true; - break; - } - } - return valid; -} // -- private slots @@ -1892,9 +1881,9 @@ void IntroductionPage::setPackageManager(bool value) */ void IntroductionPage::initializePage() { - const bool repositoriesAvailable = validRepositoriesAvailable(); - PackageManagerCore *core = packageManagerCore(); + const bool repositoriesAvailable = core->validRepositoriesAvailable(); + if (core->isPackageManager()) { m_packageManager->setChecked(true); } else if (core->isUpdater()) { @@ -1927,7 +1916,7 @@ void IntroductionPage::onCoreNetworkSettingsChanged() PackageManagerCore *core = packageManagerCore(); if (core->isUninstaller() || core->isMaintainer()) { - m_offlineMaintenanceTool = !validRepositoriesAvailable(); + m_offlineMaintenanceTool = !core->validRepositoriesAvailable(); setMaintainerToolsEnabled(!m_offlineMaintenanceTool); m_removeAllComponents->setChecked(m_offlineMaintenanceTool); @@ -2117,7 +2106,7 @@ void LicenseAgreementPage::entering() */ bool LicenseAgreementPage::isComplete() const { - return m_acceptCheckBox->isChecked(); + return m_acceptCheckBox->isChecked() && ProductKeyCheck::instance()->hasAcceptedAllLicenses(); } void LicenseAgreementPage::openLicenseUrl(const QUrl &url) @@ -2210,7 +2199,7 @@ void ComponentSelectionPage::entering() QT_TR_NOOP("Please select the components you want to update."), QT_TR_NOOP("Please select the components you want to install."), QT_TR_NOOP("Please select the components you want to uninstall."), - QT_TR_NOOP("Select the components to install. Deselect installed components to uninstall them. Any components already installed will not be updated."), + QT_TR_NOOP("Select the components to install. Deselect installed components to uninstall them.<br>Any components already installed will not be updated."), QT_TR_NOOP("Mandatory components need to be updated first before you can select other components to update.") }; @@ -2228,14 +2217,13 @@ void ComponentSelectionPage::entering() d->onModelStateChanged(d->m_currentModel->checkedState()); setModified(isComplete()); - if (core->settings().repositoryCategories().count() > 0 && !core->isOfflineOnly() - && !core->isUpdater()) { - d->showCategoryLayout(true); - core->settings().setAllowUnstableComponents(true); - } else { - d->showCategoryLayout(false); - } + d->showCategoryLayout(core->showRepositoryCategories()); d->showCompressedRepositoryButton(); + d->showCreateOfflineInstallerButton(true); + + // Reset to default supplement state. The page may set it to OfflineGenerator + // which needs to be reset after navigating back to the page. + core->resetBinaryMarkerSupplement(); } /*! @@ -2245,6 +2233,7 @@ void ComponentSelectionPage::entering() void ComponentSelectionPage::leaving() { d->hideCompressedRepositoryButton(); + d->showCreateOfflineInstallerButton(false); } /*! @@ -2328,16 +2317,6 @@ void ComponentSelectionPage::deselectComponent(const QString &id) } /*! - Adds the possibility to install a compressed repository on component selection - page. A new button which opens a file browser is added for compressed - repository selection. -*/ -void ComponentSelectionPage::allowCompressedRepositoryInstall() -{ - d->allowCompressedRepositoryInstall(); -} - -/*! Adds an additional virtual component with the \a name to be installed. Returns \c true if the virtual component is found and not installed. @@ -2357,6 +2336,11 @@ bool ComponentSelectionPage::addVirtualComponentToUninstall(const QString &name) return false; } +void ComponentSelectionPage::setAllowCreateOfflineInstaller(bool allow) +{ + d->setAllowCreateOfflineInstaller(allow); +} + void ComponentSelectionPage::setModified(bool modified) { setComplete(modified); @@ -2370,18 +2354,7 @@ bool ComponentSelectionPage::isComplete() const if (!d->componentsResolved()) return false; - if (packageManagerCore()->isInstaller() || packageManagerCore()->isUpdater()) - return d->m_currentModel->checked().count(); - - if (d->m_currentModel->checkedState().testFlag(ComponentModel::DefaultChecked) == false) - return true; - - const QSet<Component *> uncheckable = d->m_currentModel->uncheckable(); - for (auto &component : uncheckable) { - if (component->forcedInstallation() && !component->isInstalled()) - return true; // allow installation for new forced components - } - return false; + return d->m_currentModel->componentsSelected(); } @@ -2719,6 +2692,10 @@ void ReadyForInstallationPage::entering() setButtonText(QWizard::CommitButton, tr("U&pdate")); setColoredTitle(tr("Ready to Update Packages")); m_msgLabel->setText(tr("All required information is now available to begin updating your installation.")); + } else if (packageManagerCore()->isOfflineGenerator()) { + setButtonText(QWizard::CommitButton, tr("Create Offline Installer")); + setColoredTitle(tr("Ready to Create Offline Installer")); + m_msgLabel->setText(tr("All required information is now available to create an offline installer for selected components.")); } else { Q_ASSERT(packageManagerCore()->isInstaller()); setButtonText(QWizard::CommitButton, tr("&Install")); @@ -2735,11 +2712,10 @@ void ReadyForInstallationPage::entering() m_taskDetailsBrowser->setVisible(!componentsOk || LoggingHandler::instance().isVerbose()); setComplete(componentsOk); - QString spaceInfo; - if (packageManagerCore()->checkAvailableSpace(spaceInfo)) { - m_msgLabel->setText(QString::fromLatin1("%1 %2").arg(m_msgLabel->text(), spaceInfo)); + if (packageManagerCore()->checkAvailableSpace()) { + m_msgLabel->setText(QString::fromLatin1("%1 %2").arg(m_msgLabel->text(), packageManagerCore()->availableSpaceMessage())); } else { - m_msgLabel->setText(spaceInfo); + m_msgLabel->setText(packageManagerCore()->availableSpaceMessage()); setComplete(false); } } @@ -2759,7 +2735,9 @@ void ReadyForInstallationPage::leaving() void ReadyForInstallationPage::updatePageListTitle() { PackageManagerCore *core = packageManagerCore(); - if (core->isInstaller()) + if (core->isOfflineGenerator()) + setPageListTitle(tr("Ready to Create Offline Installer")); + else if (core->isInstaller()) setPageListTitle(tr("Ready to Install")); else if (core->isMaintainer()) setPageListTitle(tr("Ready to Update")); @@ -2817,6 +2795,11 @@ PerformInstallationPage::PerformInstallationPage(PackageManagerCore *core) connect(core, &PackageManagerCore::installationFinished, this, &PerformInstallationPage::installationFinished); + connect(core, &PackageManagerCore::offlineGenerationStarted, + this, &PerformInstallationPage::installationStarted); + connect(core, &PackageManagerCore::offlineGenerationFinished, + this, &PerformInstallationPage::installationFinished); + connect(core, &PackageManagerCore::uninstallationStarted, this, &PerformInstallationPage::uninstallationStarted); connect(core, &PackageManagerCore::uninstallationFinished, @@ -2886,6 +2869,11 @@ void PerformInstallationPage::entering() setColoredTitle(tr("Updating components of %1").arg(productName())); QTimer::singleShot(30, packageManagerCore(), SLOT(runPackageUpdater())); + } else if (packageManagerCore()->isOfflineGenerator()) { + setButtonText(QWizard::CommitButton, tr("&Create Offline Installer")); + setColoredTitle(tr("Creating Offline Installer for %1").arg(productName())); + + QTimer::singleShot(30, packageManagerCore(), SLOT(runOfflineGenerator())); } else { setButtonText(QWizard::CommitButton, tr("&Install")); setColoredTitle(tr("Installing %1").arg(productName())); @@ -2910,7 +2898,9 @@ void PerformInstallationPage::leaving() void PerformInstallationPage::updatePageListTitle() { PackageManagerCore *core = packageManagerCore(); - if (core->isInstaller()) + if (core->isOfflineGenerator()) + setPageListTitle(tr("Creating Offline Installer")); + else if (core->isInstaller()) setPageListTitle(tr("Installing")); else if (core->isMaintainer()) setPageListTitle(tr("Updating")); @@ -3014,7 +3004,7 @@ FinishedPage::FinishedPage(PackageManagerCore *core) , m_commitButton(nullptr) { setObjectName(QLatin1String("FinishedPage")); - setColoredTitle(tr("Completing the %1 Setup").arg(productName())); + setColoredTitle(tr("Finished the %1 Setup").arg(productName())); setPageListTitle(tr("Finished")); m_msgLabel = new QLabel(this); @@ -3039,7 +3029,7 @@ FinishedPage::FinishedPage(PackageManagerCore *core) */ void FinishedPage::entering() { - m_msgLabel->setText(tr("Click %1 to exit the %2 Wizard.") + m_msgLabel->setText(tr("Click %1 to exit the %2 Setup.") .arg(gui()->defaultButtonText(QWizard::FinishButton).remove(QLatin1Char('&'))) .arg(productName())); @@ -3132,16 +3122,8 @@ void FinishedPage::leaving() */ void FinishedPage::handleFinishClicked() { - const QString program = - packageManagerCore()->replaceVariables(packageManagerCore()->value(scRunProgram)); - - const QStringList args = packageManagerCore()->replaceVariables(packageManagerCore() - ->values(scRunProgramArguments)); - if (!m_runItCheckBox->isChecked() || program.isEmpty()) - return; - - qCDebug(QInstaller::lcInstallerInstallLog) << "starting" << program << args; - QProcess::startDetached(program, args); + if (m_runItCheckBox->isChecked()) + packageManagerCore()->runProgram(); } /*! @@ -3186,7 +3168,7 @@ RestartPage::RestartPage(PackageManagerCore *core) : PackageManagerPage(core) { setObjectName(QLatin1String("RestartPage")); - setColoredTitle(tr("Completing the %1 Setup").arg(productName())); + setColoredTitle(tr("Finished the %1 Setup").arg(productName())); // Never show this page on the page list setShowOnPageList(false); diff --git a/src/libs/installer/packagemanagergui.h b/src/libs/installer/packagemanagergui.h index ba27c4af6..d83643005 100644 --- a/src/libs/installer/packagemanagergui.h +++ b/src/libs/installer/packagemanagergui.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -82,6 +82,7 @@ public: void clickButton(int wizardButton, int delayInMs = 0); void clickButton(const QString &objectName, int delayInMs = 0) const; bool isButtonEnabled(int wizardButton); + void setWizardPageButtonText(int pageId, int buttonId, const QString &buttonText); void showSettingsButton(bool show); void requestSettingsButtonByInstaller(bool request); @@ -261,7 +262,6 @@ private: void leaving() override; void showWidgets(bool show); - bool validRepositoriesAvailable() const; private: bool m_updatesFetched; @@ -330,9 +330,10 @@ public: Q_INVOKABLE void selectDefault(); Q_INVOKABLE void selectComponent(const QString &id); Q_INVOKABLE void deselectComponent(const QString &id); - Q_INVOKABLE void allowCompressedRepositoryInstall(); Q_INVOKABLE bool addVirtualComponentToUninstall(const QString &name); + void setAllowCreateOfflineInstaller(bool allow); + protected: void entering() override; void leaving() override; diff --git a/src/libs/installer/packagesource.cpp b/src/libs/installer/packagesource.cpp index 3b9fbe813..0f87e0def 100644 --- a/src/libs/installer/packagesource.cpp +++ b/src/libs/installer/packagesource.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -50,7 +50,7 @@ namespace QInstaller { */ /*! - \fn QInstaller::PackageSource::PackageSource(const QUrl &u, int p) + \fn QInstaller::PackageSource::PackageSource(const QUrl &u, int p, bool pl) Constructs a package source info object. The object's url is set to \a u, while the priority is set to \a p. @@ -69,7 +69,7 @@ namespace QInstaller { /*! Returns the hash value for the \a key, using \a seed to seed the calculation. */ -uint qHash(const PackageSource &key, uint seed) +hashValue qHash(const PackageSource &key, hashValue seed) { return qHash(key.url, seed) ^ key.priority; } diff --git a/src/libs/installer/packagesource.h b/src/libs/installer/packagesource.h index 1193c1f76..f63b53cd8 100644 --- a/src/libs/installer/packagesource.h +++ b/src/libs/installer/packagesource.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -40,16 +40,18 @@ struct INSTALLER_EXPORT PackageSource PackageSource() : priority(-1) {} - PackageSource(const QUrl &u, int p) + PackageSource(const QUrl &u, int p, bool pl = false) : url(u) , priority(p) + , postLoadComponentScript(pl) {} QUrl url; int priority; + bool postLoadComponentScript; }; -INSTALLER_EXPORT uint qHash(const PackageSource &key, uint seed); +INSTALLER_EXPORT hashValue qHash(const PackageSource &key, hashValue seed); INSTALLER_EXPORT bool operator==(const PackageSource &lhs, const PackageSource &rhs); } // namespace QInstaller diff --git a/src/libs/installer/permissionsettings.cpp b/src/libs/installer/permissionsettings.cpp index 68e4ab427..d70cf5625 100644 --- a/src/libs/installer/permissionsettings.cpp +++ b/src/libs/installer/permissionsettings.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -37,38 +37,6 @@ using namespace QInstaller; \internal */ -PermissionSettings::PermissionSettings(const QString &organization, const QString &application, QObject *parent) - : QSettings(organization, application, parent) -{ -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - setIniCodec("UTF-8"); // to workaround QTBUG-102334 -#endif -} - -PermissionSettings::PermissionSettings(Scope scope, const QString &organization, const QString &application, QObject *parent) - : QSettings(scope, organization, application, parent) -{ -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - setIniCodec("UTF-8"); // QTBUG-102334 -#endif -} - -PermissionSettings::PermissionSettings(Format format, Scope scope, const QString &organization, const QString &application, QObject *parent) - : QSettings(format, scope, organization, application, parent) -{ -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - setIniCodec("UTF-8"); // QTBUG-102334 -#endif -} - -PermissionSettings::PermissionSettings(const QString &fileName, Format format, QObject *parent) - : QSettings(fileName, format, parent) -{ -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - setIniCodec("UTF-8"); // QTBUG-102334 -#endif -} - PermissionSettings::~PermissionSettings() { if (!fileName().isEmpty()) { diff --git a/src/libs/installer/permissionsettings.h b/src/libs/installer/permissionsettings.h index 2621624c2..c950d9c17 100644 --- a/src/libs/installer/permissionsettings.h +++ b/src/libs/installer/permissionsettings.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -37,12 +37,19 @@ class PermissionSettings : public QSettings { public: explicit PermissionSettings(const QString &organization, - const QString &application = QString(), QObject *parent = 0); + const QString &application = QString(), QObject *parent = 0) + : QSettings(organization, application, parent) {} + PermissionSettings(Scope scope, const QString &organization, - const QString &application = QString(), QObject *parent = 0); + const QString &application = QString(), QObject *parent = 0) + : QSettings(scope, organization, application, parent) {} + PermissionSettings(Format format, Scope scope, const QString &organization, - const QString &application = QString(), QObject *parent = 0); - PermissionSettings(const QString &fileName, Format format, QObject *parent = 0); + const QString &application = QString(), QObject *parent = 0) + : QSettings(format, scope, organization, application, parent) {} + PermissionSettings(const QString &fileName, Format format, QObject *parent = 0) + : QSettings(fileName, format, parent) {} + ~PermissionSettings(); }; diff --git a/src/libs/installer/productkeycheck.cpp b/src/libs/installer/productkeycheck.cpp index c1dfe83d6..ed128fa61 100644 --- a/src/libs/installer/productkeycheck.cpp +++ b/src/libs/installer/productkeycheck.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -29,6 +29,8 @@ #include "productkeycheck.h" #include "packagemanagercore.h" +#include <QtUiTools/QUiLoader> + class ProductKeyCheckPrivate { }; @@ -49,6 +51,12 @@ ProductKeyCheck *ProductKeyCheck::instance() return &instance; } +QUiLoader *ProductKeyCheck::uiLoader() +{ + static QUiLoader loader; + return &loader; +} + void ProductKeyCheck::init(QInstaller::PackageManagerCore *core) { Q_UNUSED(core) @@ -106,3 +114,22 @@ bool ProductKeyCheck::hasValidLicense() const { return true; } + +bool ProductKeyCheck::hasAcceptedAllLicenses() const +{ + return true; +} + +QString ProductKeyCheck::licenseAcceptanceText() const +{ + return QString(); +} +QString ProductKeyCheck::securityWarning() const +{ + return QString(); +} + +QString ProductKeyCheck::additionalMetaDownloadWarning() const +{ + return QString(); +} diff --git a/src/libs/installer/productkeycheck.h b/src/libs/installer/productkeycheck.h index b7e8c6d52..8e7d6724f 100644 --- a/src/libs/installer/productkeycheck.h +++ b/src/libs/installer/productkeycheck.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -32,6 +32,7 @@ #include "installer_global.h" #include <QString> +#include <QUiLoader> namespace QInstaller { @@ -49,6 +50,8 @@ public: static ProductKeyCheck *instance(); void init(QInstaller::PackageManagerCore *core); + static QUiLoader *uiLoader(); + // was validLicense bool hasValidKey(); QString lastErrorString(); @@ -67,6 +70,10 @@ public: QList<int> registeredPages() const; bool hasValidLicense() const; + bool hasAcceptedAllLicenses() const; + QString licenseAcceptanceText() const; + QString securityWarning() const; + QString additionalMetaDownloadWarning() const; private: ProductKeyCheck(); diff --git a/src/libs/installer/progresscoordinator.cpp b/src/libs/installer/progresscoordinator.cpp index 8b02711b1..6413efe28 100644 --- a/src/libs/installer/progresscoordinator.cpp +++ b/src/libs/installer/progresscoordinator.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -40,7 +40,7 @@ using namespace QInstaller; QT_BEGIN_NAMESPACE -uint qHash(QPointer<QObject> key) +hashValue qHash(QPointer<QObject> key) { return qHash(key.data()); } @@ -76,7 +76,8 @@ ProgressCoordinator *ProgressCoordinator::instance() void ProgressCoordinator::reset() { - disconnectAllSenders(); + m_senderPartProgressSizeHash.clear(); + m_senderPendingCalculatedPercentageHash.clear(); m_installationLabelText.clear(); m_currentCompletePercentage = 0; m_currentBasePercentage = 0; @@ -90,10 +91,11 @@ void ProgressCoordinator::reset() void ProgressCoordinator::registerPartProgress(QObject *sender, const char *signal, double partProgressSize) { Q_ASSERT(sender); + Q_ASSERT(!sender->objectName().isEmpty()); Q_ASSERT(QString::fromLatin1(signal).contains(QLatin1String("(double)"))); Q_ASSERT(partProgressSize <= 1); - m_senderPartProgressSizeHash.insert(sender, partProgressSize); + m_senderPartProgressSizeHash.insert(sender->objectName(), partProgressSize); bool isConnected = connect(sender, signal, this, SLOT(partProgressChanged(double))); Q_UNUSED(isConnected); Q_ASSERT(isConnected); @@ -116,16 +118,17 @@ void ProgressCoordinator::partProgressChanged(double fraction) } // no fraction no change - if (fraction == 0) + if (fraction == 0 || !sender()) return; + QString senderObjectName = sender()->objectName(); // ignore senders sending 100% multiple times - if (fraction == 1 && m_senderPendingCalculatedPercentageHash.contains(sender()) - && m_senderPendingCalculatedPercentageHash.value(sender()) == 0) { + if (fraction == 1 && m_senderPendingCalculatedPercentageHash.contains(senderObjectName) + && m_senderPendingCalculatedPercentageHash.value(senderObjectName) == 0) { return; } - double partProgressSize = m_senderPartProgressSizeHash.value(sender(), 0); + double partProgressSize = m_senderPartProgressSizeHash.value(senderObjectName, 0); if (partProgressSize == 0) { qCWarning(QInstaller::lcInstallerInstallLog) << "It seems that this sender was not registered " "in the right way:" << sender(); @@ -138,7 +141,7 @@ void ProgressCoordinator::partProgressChanged(double fraction) // allPendingCalculatedPartPercentages has negative values double newCurrentCompletePercentage = m_currentBasePercentage - pendingCalculatedPartPercentage - + allPendingCalculatedPartPercentages(sender()); + + allPendingCalculatedPartPercentages(senderObjectName); //we can't check this here, because some round issues can make it little bit under 0 or over 100 //Q_ASSERT(newCurrentCompletePercentage >= 0); @@ -163,9 +166,9 @@ void ProgressCoordinator::partProgressChanged(double fraction) m_currentCompletePercentage = newCurrentCompletePercentage; if (fraction == 1) { m_currentBasePercentage = m_currentBasePercentage - pendingCalculatedPartPercentage; - m_senderPendingCalculatedPercentageHash.insert(sender(), 0); + m_senderPendingCalculatedPercentageHash.insert(senderObjectName, 0); } else { - m_senderPendingCalculatedPercentageHash.insert(sender(), pendingCalculatedPartPercentage); + m_senderPendingCalculatedPercentageHash.insert(senderObjectName, pendingCalculatedPartPercentage); } } else { //if (m_undoMode) @@ -174,7 +177,7 @@ void ProgressCoordinator::partProgressChanged(double fraction) //double checkValue = allPendingCalculatedPartPercentages(sender()); double newCurrentCompletePercentage = m_manualAddedPercentage + m_currentBasePercentage - + pendingCalculatedPartPercentage + allPendingCalculatedPartPercentages(sender()); + + pendingCalculatedPartPercentage + allPendingCalculatedPartPercentages(senderObjectName); //we can't check this here, because some round issues can make it little bit under 0 or over 100 //Q_ASSERT(newCurrentCompletePercentage >= 0); @@ -199,9 +202,9 @@ void ProgressCoordinator::partProgressChanged(double fraction) if (fraction == 1) { m_currentBasePercentage = m_currentBasePercentage + pendingCalculatedPartPercentage; - m_senderPendingCalculatedPercentageHash.insert(sender(), 0); + m_senderPendingCalculatedPercentageHash.insert(senderObjectName, 0); } else { - m_senderPendingCalculatedPercentageHash.insert(sender(), pendingCalculatedPartPercentage); + m_senderPendingCalculatedPercentageHash.insert(senderObjectName, pendingCalculatedPartPercentage); } } //if (m_undoMode) printProgressPercentage(progressInPercentage()); @@ -219,25 +222,13 @@ int ProgressCoordinator::progressInPercentage() const return currentValue; } -void ProgressCoordinator::disconnectAllSenders() -{ - foreach (QPointer<QObject> sender, m_senderPartProgressSizeHash.keys()) { - if (!sender.isNull()) { - bool isDisconnected = sender->disconnect(this); - Q_UNUSED(isDisconnected); - Q_ASSERT(isDisconnected); - } - } - m_senderPartProgressSizeHash.clear(); - m_senderPendingCalculatedPercentageHash.clear(); -} - void ProgressCoordinator::setUndoMode() { Q_ASSERT(!m_undoMode); m_undoMode = true; - disconnectAllSenders(); + m_senderPartProgressSizeHash.clear(); + m_senderPendingCalculatedPercentageHash.clear(); m_reachedPercentageBeforeUndo = progressInPercentage(); m_currentBasePercentage = m_reachedPercentageBeforeUndo; } @@ -294,10 +285,10 @@ void ProgressCoordinator::emitLabelAndDetailTextChanged(const QString &text) qApp->processEvents(); //makes the result available in the ui } -double ProgressCoordinator::allPendingCalculatedPartPercentages(QObject *excludeKeyObject) +double ProgressCoordinator::allPendingCalculatedPartPercentages(const QString &excludeKeyObject) { double result = 0; - QHash<QPointer<QObject>, double>::iterator it = m_senderPendingCalculatedPercentageHash.begin(); + QHash<QString, double>::iterator it = m_senderPendingCalculatedPercentageHash.begin(); while (it != m_senderPendingCalculatedPercentageHash.end()) { if (it.key() != excludeKeyObject) result += it.value(); diff --git a/src/libs/installer/progresscoordinator.h b/src/libs/installer/progresscoordinator.h index 3540b5d16..75d5a5d30 100644 --- a/src/libs/installer/progresscoordinator.h +++ b/src/libs/installer/progresscoordinator.h @@ -85,12 +85,12 @@ protected: explicit ProgressCoordinator(QObject *parent); private: - double allPendingCalculatedPartPercentages(QObject *excludeKeyObject = 0); + double allPendingCalculatedPartPercentages(const QString &excludeKeyObject = QString()); void disconnectAllSenders(); private: - QHash<QPointer<QObject>, double> m_senderPendingCalculatedPercentageHash; - QHash<QPointer<QObject>, double> m_senderPartProgressSizeHash; + QHash<QString, double> m_senderPendingCalculatedPercentageHash; + QHash<QString, double> m_senderPartProgressSizeHash; ProgressSpinner *m_progressSpinner; QString m_installationLabelText; double m_currentCompletePercentage; diff --git a/src/libs/installer/protocol.h b/src/libs/installer/protocol.h index 2dd1a5b1c..8b2288a89 100644 --- a/src/libs/installer/protocol.h +++ b/src/libs/installer/protocol.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -90,7 +90,7 @@ const char GetQProcessSignals[] = "GetQProcessSignals"; const char QProcessSignalBytesWritten[] = "QProcess::bytesWritten"; const char QProcessSignalAboutToClose[] = "QProcess::aboutToClose"; const char QProcessSignalReadChannelFinished[] = "QProcess::readChannelFinished"; -const char QProcessSignalError[] = "QProcess::error"; +const char QProcessSignalError[] = "QProcess::errorOccurred"; const char QProcessSignalReadyReadStandardOutput[] = "QProcess::readyReadStandardOutput"; const char QProcessSignalReadyReadStandardError[] = "QProcess::readyReadStandardError"; const char QProcessSignalStarted[] = "QProcess::started"; diff --git a/src/libs/installer/qprocesswrapper.cpp b/src/libs/installer/qprocesswrapper.cpp index 6f34e36da..44117eefb 100644 --- a/src/libs/installer/qprocesswrapper.cpp +++ b/src/libs/installer/qprocesswrapper.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -54,7 +54,7 @@ QProcessWrapper::QProcessWrapper(QObject *parent) connect(&process, &QIODevice::bytesWritten, this, &QProcessWrapper::bytesWritten); connect(&process, &QIODevice::aboutToClose, this, &QProcessWrapper::aboutToClose); connect(&process, &QIODevice::readChannelFinished, this, &QProcessWrapper::readChannelFinished); - connect(&process, SIGNAL(error(QProcess::ProcessError)), SIGNAL(error(QProcess::ProcessError))); + connect(&process, SIGNAL(errorOccurred(QProcess::ProcessError)), SIGNAL(errorOccurred(QProcess::ProcessError))); connect(&process, &QProcess::readyReadStandardOutput, this, &QProcessWrapper::readyReadStandardOutput); connect(&process, &QProcess::readyReadStandardError, this, &QProcessWrapper::readyReadStandardError); connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), SIGNAL(finished(int,QProcess::ExitStatus))); @@ -88,7 +88,7 @@ void QProcessWrapper::processSignals() } else if (name == QLatin1String(Protocol::QProcessSignalReadChannelFinished)) { emit readChannelFinished(); } else if (name == QLatin1String(Protocol::QProcessSignalError)) { - emit error(static_cast<QProcess::ProcessError> (receivedSignals.takeFirst().toInt())); + emit errorOccurred(static_cast<QProcess::ProcessError> (receivedSignals.takeFirst().toInt())); } else if (name == QLatin1String(Protocol::QProcessSignalReadyReadStandardOutput)) { emit readyReadStandardOutput(); } else if (name == QLatin1String(Protocol::QProcessSignalReadyReadStandardError)) { @@ -191,8 +191,8 @@ void QProcessWrapper::setProcessChannelMode(QProcessWrapper::ProcessChannelMode { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessSetProcessChannelMode), - static_cast<QProcess::ProcessChannelMode>(mode), dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessSetProcessChannelMode), + static_cast<QProcess::ProcessChannelMode>(mode)); m_lock.unlock(); } else { process.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(mode)); @@ -216,8 +216,8 @@ void QProcessWrapper::setReadChannel(QProcessWrapper::ProcessChannel chan) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessSetReadChannel), - static_cast<QProcess::ProcessChannel>(chan), dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessSetReadChannel), + static_cast<QProcess::ProcessChannel>(chan)); m_lock.unlock(); } else { process.setReadChannel(static_cast<QProcess::ProcessChannel>(chan)); @@ -263,7 +263,7 @@ void QProcessWrapper::closeWriteChannel() { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessCloseWriteChannel)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessCloseWriteChannel)); m_lock.unlock(); } else { process.closeWriteChannel(); @@ -296,7 +296,7 @@ void QProcessWrapper::kill() { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessKill)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessKill)); m_lock.unlock(); } else { process.kill(); @@ -343,7 +343,7 @@ void QProcessWrapper::start(const QString ¶m1, const QStringList ¶m2, { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessStart3Arg), param1, param2, param3); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessStart3Arg), param1, param2, param3); m_lock.unlock(); } else { process.start(param1, param2, param3); @@ -354,7 +354,7 @@ void QProcessWrapper::start(const QString ¶m1, QIODevice::OpenMode param2) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessStart2Arg), param1, param2); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessStart2Arg), param1, param2); m_lock.unlock(); } else { process.start(param1, {}, param2); @@ -376,7 +376,7 @@ void QProcessWrapper::terminate() { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessTerminate)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessTerminate)); m_lock.unlock(); } else { process.terminate(); @@ -443,7 +443,7 @@ void QProcessWrapper::setEnvironment(const QStringList ¶m1) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessSetEnvironment), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessSetEnvironment), param1); m_lock.unlock(); } else { process.setEnvironment(param1); @@ -455,7 +455,7 @@ void QProcessWrapper::setNativeArguments(const QString ¶m1) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessSetNativeArguments), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessSetNativeArguments), param1); m_lock.unlock(); } else { process.setNativeArguments(param1); @@ -467,7 +467,7 @@ void QProcessWrapper::setWorkingDirectory(const QString ¶m1) { if (connectToServer()) { m_lock.lockForWrite(); - callRemoteMethod(QLatin1String(Protocol::QProcessSetWorkingDirectory), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QProcessSetWorkingDirectory), param1); m_lock.unlock(); } else { process.setWorkingDirectory(param1); diff --git a/src/libs/installer/qprocesswrapper.h b/src/libs/installer/qprocesswrapper.h index 1a68bbf92..b34d6f82c 100644 --- a/src/libs/installer/qprocesswrapper.h +++ b/src/libs/installer/qprocesswrapper.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -119,7 +119,7 @@ Q_SIGNALS: void bytesWritten(qint64); void aboutToClose(); void readChannelFinished(); - void error(QProcess::ProcessError); + void errorOccurred(QProcess::ProcessError); void readyReadStandardOutput(); void readyReadStandardError(); void finished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/src/libs/installer/qsettingswrapper.cpp b/src/libs/installer/qsettingswrapper.cpp index f2dd53767..d322728f5 100644 --- a/src/libs/installer/qsettingswrapper.cpp +++ b/src/libs/installer/qsettingswrapper.cpp @@ -141,27 +141,39 @@ QString QSettingsWrapper::applicationName() const return d->settings.applicationName(); } -void QSettingsWrapper::beginGroup(const QString ¶m1) +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + void QSettingsWrapper::beginGroup(const QString &prefix) +#else + void QSettingsWrapper::beginGroup(QAnyStringView prefix) +#endif { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsBeginGroup), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsBeginGroup), prefix); else - d->settings.beginGroup(param1); + d->settings.beginGroup(prefix); } -int QSettingsWrapper::beginReadArray(const QString ¶m1) +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + int QSettingsWrapper::beginReadArray(const QString &prefix) +#else + int QSettingsWrapper::beginReadArray(QAnyStringView prefix) +#endif { if (createSocket()) - return callRemoteMethod<qint32>(QLatin1String(Protocol::QSettingsBeginReadArray), param1); - return d->settings.beginReadArray(param1); + return callRemoteMethod<qint32>(QLatin1String(Protocol::QSettingsBeginReadArray), prefix); + return d->settings.beginReadArray(prefix); } -void QSettingsWrapper::beginWriteArray(const QString ¶m1, int param2) +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + void QSettingsWrapper::beginWriteArray(const QString &prefix, int size) +#else + void QSettingsWrapper::beginWriteArray(QAnyStringView prefix, int size) +#endif { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsBeginWriteArray), param1, qint32(param2)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsBeginWriteArray), prefix, qint32(size)); else - d->settings.beginWriteArray(param1, param2); + d->settings.beginWriteArray(prefix, size); } QStringList QSettingsWrapper::childGroups() const @@ -181,21 +193,25 @@ QStringList QSettingsWrapper::childKeys() const void QSettingsWrapper::clear() { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsClear)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsClear)); else d->settings.clear(); } -bool QSettingsWrapper::contains(const QString ¶m1) const +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + bool QSettingsWrapper::contains(const QString &key) const +#else + bool QSettingsWrapper::contains(QAnyStringView key) const +#endif { if (createSocket()) - return callRemoteMethod<bool>(QLatin1String(Protocol::QSettingsContains), param1); - return d->settings.contains(param1); + return callRemoteMethod<bool>(QLatin1String(Protocol::QSettingsContains), key); + return d->settings.contains(key); } void QSettingsWrapper::endArray() { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsEndArray)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsEndArray)); else d->settings.endArray(); } @@ -203,7 +219,7 @@ void QSettingsWrapper::endArray() void QSettingsWrapper::endGroup() { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsEndGroup)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsEndGroup)); else d->settings.endGroup(); } @@ -249,12 +265,16 @@ QString QSettingsWrapper::organizationName() const return d->settings.organizationName(); } -void QSettingsWrapper::remove(const QString ¶m1) +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + void QSettingsWrapper::remove(const QString &key) +#else + void QSettingsWrapper::remove(QAnyStringView key) +#endif { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsRemove), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsRemove), key); else - d->settings.remove(param1); + d->settings.remove(key); } QSettingsWrapper::Scope QSettingsWrapper::scope() const @@ -266,7 +286,7 @@ QSettingsWrapper::Scope QSettingsWrapper::scope() const void QSettingsWrapper::setArrayIndex(int param1) { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsSetArrayIndex), qint32(param1), dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsSetArrayIndex), qint32(param1)); else d->settings.setArrayIndex(param1); } @@ -274,17 +294,20 @@ void QSettingsWrapper::setArrayIndex(int param1) void QSettingsWrapper::setFallbacksEnabled(bool param1) { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsSetFallbacksEnabled), param1, dummy); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsSetFallbacksEnabled), param1); else d->settings.setFallbacksEnabled(param1); } - -void QSettingsWrapper::setValue(const QString ¶m1, const QVariant ¶m2) +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) +void QSettingsWrapper::setValue(const QString &key, const QVariant &value) +#else +void QSettingsWrapper::setValue(QAnyStringView key, const QVariant &value) +#endif { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsSetValue), param1, param2); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsSetValue), key, value); else - d->settings.setValue(param1, param2); + d->settings.setValue(key, value); } QSettingsWrapper::Status QSettingsWrapper::status() const @@ -299,17 +322,30 @@ QSettingsWrapper::Status QSettingsWrapper::status() const void QSettingsWrapper::sync() { if (createSocket()) - callRemoteMethod(QLatin1String(Protocol::QSettingsSync)); + callRemoteMethodDefaultReply(QLatin1String(Protocol::QSettingsSync)); else d->settings.sync(); } -QVariant QSettingsWrapper::value(const QString ¶m1, const QVariant ¶m2) const +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) +QVariant QSettingsWrapper::value(const QString &key, const QVariant &value) const +#else +QVariant QSettingsWrapper::value(QAnyStringView key, const QVariant &value) const +#endif +{ + if (createSocket()) + return callRemoteMethod<QVariant>(QLatin1String(Protocol::QSettingsValue), key, value); + return d->settings.value(key, value); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) +QVariant QSettingsWrapper::value(QAnyStringView key) const { if (createSocket()) - return callRemoteMethod<QVariant>(QLatin1String(Protocol::QSettingsValue), param1, param2); - return d->settings.value(param1, param2); + return callRemoteMethod<QVariant>(QLatin1String(Protocol::QSettingsValue), key); + return d->settings.value(key); } +#endif // -- private diff --git a/src/libs/installer/qsettingswrapper.h b/src/libs/installer/qsettingswrapper.h index 92221c117..f5d428b1e 100644 --- a/src/libs/installer/qsettingswrapper.h +++ b/src/libs/installer/qsettingswrapper.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -36,6 +36,8 @@ #include <QSettings> +QT_FORWARD_DECLARE_CLASS(QTextCodec) + namespace QInstaller { class INSTALLER_EXPORT QSettingsWrapper : public RemoteObject @@ -68,12 +70,21 @@ public: void sync(); Status status() const; +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) void beginGroup(const QString &prefix); +#else + void beginGroup(QAnyStringView prefix); +#endif void endGroup(); QString group() const; +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) int beginReadArray(const QString &prefix); void beginWriteArray(const QString &prefix, int size = -1); +#else + int beginReadArray(QAnyStringView prefix); + void beginWriteArray(QAnyStringView prefix, int size = -1); +#endif void endArray(); void setArrayIndex(int i); @@ -82,12 +93,22 @@ public: QStringList childGroups() const; bool isWritable() const; +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) void setValue(const QString &key, const QVariant &value); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; +#else + void setValue(QAnyStringView key, const QVariant &value); + QVariant value(QAnyStringView key, const QVariant &defaultValue) const; + QVariant value(QAnyStringView key) const; +#endif +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) void remove(const QString &key); bool contains(const QString &key) const; - +#else + void remove(QAnyStringView key); + bool contains(QAnyStringView key) const; +#endif void setFallbacksEnabled(bool b); bool fallbacksEnabled() const; @@ -105,14 +126,13 @@ private: // we cannot support the following functionality : RemoteObject(QLatin1String(Protocol::QSettings), parent) {} +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) void setIniCodec(QTextCodec * /*codec*/); void setIniCodec(const char * /*codecName*/); QTextCodec *iniCodec() const { return 0; } - +#endif static void setDefaultFormat(QSettings::Format /*format*/); static QSettings::Format defaultFormat() { return QSettings::NativeFormat; } - static void setSystemIniPath(const QString & /*dir*/); - static void setUserIniPath(const QString & /*dir*/); static void setPath(QSettings::Format /*format*/, Scope /*scope*/, const QString & /*path*/); typedef QMap<QString, QVariant> SettingsMap; diff --git a/src/libs/installer/registerfiletypeoperation.cpp b/src/libs/installer/registerfiletypeoperation.cpp index 1754b664d..852714dfb 100644 --- a/src/libs/installer/registerfiletypeoperation.cpp +++ b/src/libs/installer/registerfiletypeoperation.cpp @@ -153,7 +153,7 @@ bool RegisterFileTypeOperation::undoOperation() { #ifdef Q_OS_WIN ensureOptionalArgumentsRead(); - if (parseUndoOperationArguments().count() > 0) + if (skipUndoOperation()) return true; QStringList args = arguments(); diff --git a/src/libs/installer/remotefileengine.cpp b/src/libs/installer/remotefileengine.cpp index b600da618..2ead83861 100644 --- a/src/libs/installer/remotefileengine.cpp +++ b/src/libs/installer/remotefileengine.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -55,10 +55,10 @@ QAbstractFileEngine* RemoteFileEngineHandler::create(const QString &fileName) co if (fileName.isEmpty() || fileName.startsWith(QLatin1String(":"))) return 0; // empty filename or Qt resource - QScopedPointer<RemoteFileEngine> client(new RemoteFileEngine()); + std::unique_ptr<RemoteFileEngine> client(new RemoteFileEngine()); client->setFileName(fileName); if (client->isConnectedToServer()) - return client.take(); + return client.release(); return 0; } @@ -313,6 +313,7 @@ bool RemoteFileEngine::link(const QString &newName) /*! \reimp */ +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool RemoteFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const { if ((const_cast<RemoteFileEngine *>(this))->connectToServer()) { @@ -320,18 +321,38 @@ bool RemoteFileEngine::mkdir(const QString &dirName, bool createParentDirectorie dirName, createParentDirectories); } return m_fileEngine.mkdir(dirName, createParentDirectories); + +} +#else +bool RemoteFileEngine::mkdir(const QString &dirName, bool createParentDirectories, + std::optional<QFile::Permissions> permissions) const +{ + if ((const_cast<RemoteFileEngine *>(this))->connectToServer()) { + return callRemoteMethod<bool>(QString::fromLatin1(Protocol::QAbstractFileEngineMkdir), + dirName, createParentDirectories); + } + return m_fileEngine.mkdir(dirName, createParentDirectories, permissions); } +#endif /*! \reimp */ +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool RemoteFileEngine::open(QIODevice::OpenMode mode) +#else +bool RemoteFileEngine::open(QIODevice::OpenMode mode, std::optional<QFile::Permissions> permissions) +#endif { if (connectToServer()) { return callRemoteMethod<bool>(QString::fromLatin1(Protocol::QAbstractFileEngineOpen), static_cast<qint32>(mode | QIODevice::Unbuffered)); } +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) return m_fileEngine.open(mode | QIODevice::Unbuffered); +#else + return m_fileEngine.open(mode | QIODevice::Unbuffered, permissions); +#endif } /*! @@ -418,8 +439,7 @@ bool RemoteFileEngine::seek(qint64 offset) void RemoteFileEngine::setFileName(const QString &fileName) { if (connectToServer()) { - callRemoteMethod(QString::fromLatin1(Protocol::QAbstractFileEngineSetFileName), fileName, - dummy); + callRemoteMethodDefaultReply(QString::fromLatin1(Protocol::QAbstractFileEngineSetFileName), fileName); } m_fileEngine.setFileName(fileName); } @@ -534,7 +554,11 @@ bool RemoteFileEngine::renameOverwrite(const QString &newName) return m_fileEngine.renameOverwrite(newName); } +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) QDateTime RemoteFileEngine::fileTime(FileTime time) const +#else +QDateTime RemoteFileEngine::fileTime(QFile::FileTime time) const +#endif { if ((const_cast<RemoteFileEngine *>(this))->connectToServer()) { return callRemoteMethod<QDateTime> diff --git a/src/libs/installer/remotefileengine.h b/src/libs/installer/remotefileengine.h index 35ebf7742..c46e861ca 100644 --- a/src/libs/installer/remotefileengine.h +++ b/src/libs/installer/remotefileengine.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -53,7 +53,12 @@ public: RemoteFileEngine(); ~RemoteFileEngine(); +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool open(QIODevice::OpenMode mode) override; +#else + bool open(QIODevice::OpenMode mode, + std::optional<QFile::Permissions> permissions = std::nullopt) override; +#endif bool close() override; bool flush() override; bool syncToDisk() override; @@ -66,7 +71,12 @@ public: bool rename(const QString &newName) override; bool renameOverwrite(const QString &newName) override; bool link(const QString &newName) override; +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) bool mkdir(const QString &dirName, bool createParentDirectories) const override; +#else + bool mkdir(const QString &dirName, bool createParentDirectories, + std::optional<QFile::Permissions> permissions = std::nullopt) const override; +#endif bool rmdir(const QString &dirName, bool recurseParentDirectories) const override; bool setSize(qint64 size) override; bool caseSensitive() const override; @@ -77,7 +87,11 @@ public: QString fileName(FileName file = DefaultName) const override; uint ownerId(FileOwner owner) const override; QString owner(FileOwner owner) const override; +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) QDateTime fileTime(FileTime time) const override; +#else + QDateTime fileTime(QFile::FileTime time) const override; +#endif void setFileName(const QString &fileName) override; int handle() const override; bool atEnd() const; diff --git a/src/libs/installer/remoteobject.cpp b/src/libs/installer/remoteobject.cpp index 70ab48af3..b4dd0cbb7 100644 --- a/src/libs/installer/remoteobject.cpp +++ b/src/libs/installer/remoteobject.cpp @@ -46,7 +46,6 @@ namespace QInstaller { RemoteObject::RemoteObject(const QString &wrappedType, QObject *parent) : QObject(parent) - , dummy(nullptr) , m_type(wrappedType) , m_socket(nullptr) { @@ -140,10 +139,4 @@ bool RemoteObject::isConnectedToServer() const return false; } -void RemoteObject::callRemoteMethod(const QString &name) -{ - const QString reply = sendReceivePacket<QString>(name, dummy, dummy, dummy); - Q_ASSERT(reply == QLatin1String(Protocol::DefaultReply)); -} - } // namespace QInstaller diff --git a/src/libs/installer/remoteobject.h b/src/libs/installer/remoteobject.h index c423de943..ddd512588 100644 --- a/src/libs/installer/remoteobject.h +++ b/src/libs/installer/remoteobject.h @@ -52,93 +52,58 @@ public: virtual ~RemoteObject() = 0; bool isConnectedToServer() const; - void callRemoteMethod(const QString &name); - template<typename T1, typename T2> - void callRemoteMethod(const QString &name, const T1 &arg, const T2 &arg2) + template<typename... Args> + void callRemoteMethodDefaultReply(const QString &name, const Args&... args) { - const QString reply = sendReceivePacket<QString>(name, arg, arg2, dummy); + const QString reply = sendReceivePacket<QString>(name, args...); Q_ASSERT(reply == QLatin1String(Protocol::DefaultReply)); } - template<typename T1, typename T2, typename T3> - void callRemoteMethod(const QString &name, const T1 &arg, const T2 &arg2, const T3 & arg3) + template<typename T, typename... Args> + T callRemoteMethod(const QString &name, const Args&... args) const { - const QString reply = sendReceivePacket<QString>(name, arg, arg2, arg3); - Q_ASSERT(reply == QLatin1String(Protocol::DefaultReply)); - } - - template<typename T> - T callRemoteMethod(const QString &name) const - { - return sendReceivePacket<T>(name, dummy, dummy, dummy); - } - - template<typename T, typename T1> - T callRemoteMethod(const QString &name, const T1 &arg) const - { - return sendReceivePacket<T>(name, arg, dummy, dummy); - } - - template<typename T, typename T1, typename T2> - T callRemoteMethod(const QString &name, const T1 & arg, const T2 &arg2) const - { - return sendReceivePacket<T>(name, arg, arg2, dummy); - } - - template<typename T, typename T1, typename T2, typename T3> - T callRemoteMethod(const QString &name, const T1 &arg, const T2 &arg2, const T3 &arg3) const - { - return sendReceivePacket<T>(name, arg, arg2, arg3); + return sendReceivePacket<T>(name, args...); } protected: bool authorize(); bool connectToServer(const QVariantList &arguments = QVariantList()); - // Use this structure to allow derived classes to manipulate the template - // function signature of the callRemoteMethod templates, since most of the - // generated functions will differ in return type rather given arguments. - struct Dummy {}; Dummy *dummy; - private: - template<typename T> bool isValueType(T) const - { - return true; - } - template<typename T> bool isValueType(T *dummy) const + template<typename T, typename... Args> + T sendReceivePacket(const QString &name, const Args&... args) const { - // Force compiler error while passing anything different then Dummy* to the function. - // It really doesn't make sense to send any pointer over to the server, so bail early. - Q_UNUSED(static_cast<Dummy*> (dummy)) - return false; - } - - template<typename T, typename T1, typename T2, typename T3> - T sendReceivePacket(const QString &name, const T1 &arg, const T2 &arg2, const T3 &arg3) const - { - writeData(name, arg, arg2, arg3); + writeData(name, args...); while (m_socket->bytesToWrite()) m_socket->waitForBytesWritten(); return readData<T>(name); } + template <class T> int writeObject(QDataStream& out, const T& t) const + { + static_assert(!std::is_pointer<T>::value, "Pointer passed to remote server"); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + out << t; +#else + if constexpr (std::is_same<T, QAnyStringView>::value) + out << t.toString(); + else + out << t; +#endif + + return 0; + } - template<typename T1, typename T2, typename T3> - void writeData(const QString &name, const T1 &arg, const T2 &arg2, const T3 &arg3) const + template<typename... Args> + void writeData(const QString &name, const Args&... args) const { QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); - if (isValueType(arg)) - out << arg; - if (isValueType(arg2)) - out << arg2; - if (isValueType(arg3)) - out << arg3; - + (void)std::initializer_list<int>{writeObject(out, args)...}; sendPacket(m_socket, name.toLatin1(), data); m_socket->flush(); } diff --git a/src/libs/installer/remoteserverconnection.cpp b/src/libs/installer/remoteserverconnection.cpp index ed3d343fe..55c4f48d9 100644 --- a/src/libs/installer/remoteserverconnection.cpp +++ b/src/libs/installer/remoteserverconnection.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -429,7 +429,8 @@ void RemoteServerConnection::handleQSettings(RemoteServerReply *reply, const QSt QString key; QVariant defaultValue; data >> key; - data >> defaultValue; + if (!data.atEnd()) + data >> defaultValue; reply->send(settings->value(key, defaultValue)); } else if (command == QLatin1String(Protocol::QSettingsOrganizationName)) { reply->send(settings->organizationName()); @@ -497,11 +498,20 @@ void RemoteServerConnection::handleQFSFileEngine(RemoteServerReply *reply, const bool createParentDirectories; data >>dirName; data >>createParentDirectories; +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) reply->send(m_engine->mkdir(dirName, createParentDirectories)); +#else + reply->send(m_engine->mkdir(dirName, createParentDirectories, std::nullopt)); +#endif + } else if (command == QLatin1String(Protocol::QAbstractFileEngineOpen)) { qint32 openMode; data >>openMode; +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) reply->send(m_engine->open(static_cast<QIODevice::OpenMode> (openMode))); +#else + reply->send(m_engine->open(static_cast<QIODevice::OpenMode> (openMode), std::nullopt)); +#endif } else if (command == QLatin1String(Protocol::QAbstractFileEngineOwner)) { qint32 owner; data >>owner; @@ -570,7 +580,11 @@ void RemoteServerConnection::handleQFSFileEngine(RemoteServerReply *reply, const } else if (command == QLatin1String(Protocol::QAbstractFileEngineFileTime)) { qint32 filetime; data >> filetime; +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) reply->send(m_engine->fileTime(static_cast<QAbstractFileEngine::FileTime> (filetime))); +#else + reply->send(m_engine->fileTime(static_cast<QFile::FileTime> (filetime))); +#endif } else if (!command.isEmpty()) { qCDebug(QInstaller::lcServer) << "Unknown QAbstractFileEngine command:" << command; } diff --git a/src/libs/installer/remoteserverconnection_p.h b/src/libs/installer/remoteserverconnection_p.h index 977a64711..09e6de7d7 100644 --- a/src/libs/installer/remoteserverconnection_p.h +++ b/src/libs/installer/remoteserverconnection_p.h @@ -52,7 +52,7 @@ private: connect(process, &QIODevice::bytesWritten, this, &QProcessSignalReceiver::onBytesWritten); connect(process, &QIODevice::aboutToClose, this, &QProcessSignalReceiver::onAboutToClose); connect(process, &QIODevice::readChannelFinished, this, &QProcessSignalReceiver::onReadChannelFinished); - connect(process, SIGNAL(error(QProcess::ProcessError)), + connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); connect(process, &QProcess::readyReadStandardOutput, this, &QProcessSignalReceiver::onReadyReadStandardOutput); diff --git a/src/libs/installer/repository.cpp b/src/libs/installer/repository.cpp index 4ef8f9f25..f7035e732 100644 --- a/src/libs/installer/repository.cpp +++ b/src/libs/installer/repository.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -35,7 +35,7 @@ #include <QDir> /*! - \fn inline uint QInstaller::qHash(const Repository &repository) + \fn inline hashValue QInstaller::qHash(const Repository &repository) Returns a hash of the \a repository. */ @@ -49,6 +49,7 @@ Repository::Repository() : m_default(false) , m_enabled(false) , m_compressed(false) + , m_postLoadComponentScript(false) { } @@ -62,9 +63,10 @@ Repository::Repository(const Repository &other) , m_username(other.m_username) , m_password(other.m_password) , m_displayname(other.m_displayname) - , m_compressed(other.m_compressed) , m_categoryname(other.m_categoryname) + , m_compressed(other.m_compressed) , m_xmlChecksum(other.m_xmlChecksum) + , m_postLoadComponentScript(other.m_postLoadComponentScript) { } @@ -77,6 +79,7 @@ Repository::Repository(const QUrl &url, bool isDefault, bool compressed) , m_default(isDefault) , m_enabled(true) , m_compressed(compressed) + , m_postLoadComponentScript(false) { } @@ -252,6 +255,22 @@ bool Repository::isCompressed() const } /*! + \internal +*/ +bool Repository::postLoadComponentScript() const +{ + return m_postLoadComponentScript; +} + +/*! + \internal +*/ +void Repository::setPostLoadComponentScript(const bool postLoad) +{ + m_postLoadComponentScript = postLoad; +} + +/*! Compares the values of this repository to \a other and returns true if they are equal (same server, default state, enabled state as well as username and password). \sa operator!=() */ @@ -259,7 +278,8 @@ bool Repository::operator==(const Repository &other) const { return m_url == other.m_url && m_default == other.m_default && m_enabled == other.m_enabled && m_username == other.m_username && m_password == other.m_password - && m_displayname == other.m_displayname && m_xmlChecksum == other.m_xmlChecksum; + && m_displayname == other.m_displayname && m_xmlChecksum == other.m_xmlChecksum + && m_postLoadComponentScript == other.m_postLoadComponentScript; } /*! @@ -288,6 +308,7 @@ const Repository &Repository::operator=(const Repository &other) m_compressed = other.m_compressed; m_categoryname = other.m_categoryname; m_xmlChecksum = other.m_xmlChecksum; + m_postLoadComponentScript = other.m_postLoadComponentScript; return *this; } @@ -307,7 +328,7 @@ QDataStream &operator>>(QDataStream &istream, Repository &repository) { QByteArray url, username, password, displayname, compressed; istream >> url >> repository.m_default >> repository.m_enabled >> username >> password - >> displayname >> repository.m_categoryname >> repository.m_xmlChecksum; + >> displayname >> repository.m_categoryname >> repository.m_xmlChecksum >> repository.m_postLoadComponentScript; repository.setUrl(QUrl::fromEncoded(QByteArray::fromBase64(url))); repository.setUsername(QString::fromUtf8(QByteArray::fromBase64(username))); repository.setPassword(QString::fromUtf8(QByteArray::fromBase64(password))); @@ -323,7 +344,7 @@ QDataStream &operator<<(QDataStream &ostream, const Repository &repository) return ostream << repository.m_url.toEncoded().toBase64() << repository.m_default << repository.m_enabled << repository.m_username.toUtf8().toBase64() << repository.m_password.toUtf8().toBase64() << repository.m_displayname.toUtf8().toBase64() << repository.m_categoryname.toUtf8().toBase64() - << repository.m_xmlChecksum.toBase64(); + << repository.m_xmlChecksum.toBase64() << repository.m_postLoadComponentScript; } } diff --git a/src/libs/installer/repository.h b/src/libs/installer/repository.h index 1edb83449..0a589a43e 100644 --- a/src/libs/installer/repository.h +++ b/src/libs/installer/repository.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -71,10 +71,13 @@ public: void setXmlChecksum(const QByteArray &checksum); bool isCompressed() const; + bool postLoadComponentScript() const; + void setPostLoadComponentScript(const bool postLoad); + bool operator==(const Repository &other) const; bool operator!=(const Repository &other) const; - uint qHash(const Repository &repository); + hashValue qHash(const Repository &repository); const Repository &operator=(const Repository &other); friend INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, Repository &repository); @@ -90,9 +93,10 @@ private: QString m_categoryname; bool m_compressed; QByteArray m_xmlChecksum; + bool m_postLoadComponentScript; }; -inline uint qHash(const Repository &repository) +inline hashValue qHash(const Repository &repository) { return qHash(repository.url()); } diff --git a/src/libs/installer/repositorycategory.cpp b/src/libs/installer/repositorycategory.cpp index 6be292330..c9bee6e3a 100644 --- a/src/libs/installer/repositorycategory.cpp +++ b/src/libs/installer/repositorycategory.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -35,7 +35,7 @@ #include <QStringList> /*! - \fn inline uint QInstaller::qHash(const RepositoryCategory &repository) + \fn inline hashValue QInstaller::qHash(const RepositoryCategory &repository) Returns a hash of the repository category \a repository. */ @@ -65,8 +65,8 @@ RepositoryCategory::RepositoryCategory() Constructs a new category by using all fields of the given category \a other. */ RepositoryCategory::RepositoryCategory(const RepositoryCategory &other) - : m_displayname(other.m_displayname), m_data(other.m_data), m_enabled(other.m_enabled), - m_tooltip(other.m_tooltip) + : m_data(other.m_data), m_displayname(other.m_displayname), m_tooltip(other.m_tooltip), + m_enabled(other.m_enabled) { registerMetaType(); } diff --git a/src/libs/installer/repositorycategory.h b/src/libs/installer/repositorycategory.h index dc45527eb..993ae78aa 100644 --- a/src/libs/installer/repositorycategory.h +++ b/src/libs/installer/repositorycategory.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -31,6 +31,7 @@ #include "installer_global.h" #include "repository.h" +#include "qinstallerglobal.h" #include <QtCore/QMetaType> #include <QtCore/QUrl> @@ -63,7 +64,7 @@ public: bool operator==(const RepositoryCategory &other) const; bool operator!=(const RepositoryCategory &other) const; - uint qHash(const RepositoryCategory &repository); + hashValue qHash(const RepositoryCategory &repository); friend INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, RepositoryCategory &repository); friend INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const RepositoryCategory &repository); @@ -75,7 +76,7 @@ private: bool m_enabled; }; -inline uint qHash(const RepositoryCategory &repository) +inline hashValue qHash(const RepositoryCategory &repository) { return qHash(repository.displayname()); } diff --git a/src/libs/installer/resources/installer.qrc b/src/libs/installer/resources/installer.qrc index 48a7c65bd..a3855b5c4 100644 --- a/src/libs/installer/resources/installer.qrc +++ b/src/libs/installer/resources/installer.qrc @@ -7,5 +7,6 @@ <file>uninstall.png</file> <file>keepinstalled.png</file> <file>keepuninstalled.png</file> + <file>qt/etc/qt.conf</file> </qresource> </RCC> diff --git a/src/libs/installer/resources/qt/etc/qt.conf b/src/libs/installer/resources/qt/etc/qt.conf new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/libs/installer/resources/qt/etc/qt.conf diff --git a/src/libs/installer/scriptengine.cpp b/src/libs/installer/scriptengine.cpp index baf348868..7e3e69eb2 100644 --- a/src/libs/installer/scriptengine.cpp +++ b/src/libs/installer/scriptengine.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2023 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -56,18 +56,6 @@ namespace QInstaller { /*! \inmodule QtInstallerFramework - \class QInstaller::ConsoleProxy - \internal -*/ - -/*! - \inmodule QtInstallerFramework - \class QInstaller::InstallerProxy - \internal -*/ - -/*! - \inmodule QtInstallerFramework \class QInstaller::QDesktopServicesProxy \internal */ @@ -78,28 +66,6 @@ namespace QInstaller { \internal */ -QJSValue InstallerProxy::components(const QString ®exp) const -{ - if (m_core) { - const QList<Component*> all = m_core->components(PackageManagerCore::ComponentType::All, regexp); - QJSValue scriptComponentsObject = m_engine->newArray(all.count()); - for (int i = 0; i < all.count(); ++i) { - Component *const component = all.at(i); - QQmlEngine::setObjectOwnership(component, QQmlEngine::CppOwnership); - scriptComponentsObject.setProperty(i, m_engine->newQObject(component)); - } - return scriptComponentsObject; - } - return m_engine->newArray(); -} - -QJSValue InstallerProxy::componentByName(const QString &componentName) -{ - if (m_core) - return m_engine->newQObject(m_core->componentByName(componentName), false); - return QJSValue(); -} - QJSValue QDesktopServicesProxy::findFiles(const QString &path, const QString &pattern) { QStringList result; @@ -223,6 +189,12 @@ bool GuiProxy::isButtonEnabled(int wizardButton) return m_gui->isButtonEnabled(wizardButton); } +void GuiProxy::setWizardPageButtonText(int pageId, int buttonId, const QString &buttonText) +{ + if (m_gui) + m_gui->setWizardPageButtonText(pageId, buttonId, buttonText); +} + void GuiProxy::showSettingsButton(bool show) { if (m_gui) @@ -382,14 +354,10 @@ ScriptEngine::ScriptEngine(PackageManagerCore *core) : QObject(core) , m_guiProxy(new GuiProxy(this, this)) , m_core(core) { - m_engine.installExtensions(QJSEngine::TranslationExtension); + m_engine.installExtensions(QJSEngine::TranslationExtension | QJSEngine::ConsoleExtension); QJSValue global = m_engine.globalObject(); - global.setProperty(QLatin1String("console"), m_engine.newQObject(new ConsoleProxy)); + global.setProperty(QLatin1String("QFileDialog"), m_engine.newQObject(new QFileDialogProxy(core))); - const QJSValue proxy = m_engine.newQObject(new InstallerProxy(this, core)); - global.setProperty(QLatin1String("InstallerProxy"), proxy); - global.setProperty(QLatin1String("print"), m_engine.newQObject(new ConsoleProxy) - .property(QLatin1String("log"))); global.setProperty(QLatin1String("systemInfo"), m_engine.newQObject(new SystemInfo)); global.setProperty(QLatin1String("QInstaller"), generateQInstallerObject()); @@ -409,11 +377,6 @@ ScriptEngine::ScriptEngine(PackageManagerCore *core) : QObject(core) global.setProperty(QLatin1String("installer"), m_engine.newQObject(new QObject)); } global.setProperty(QLatin1String("gui"), m_engine.newQObject(m_guiProxy)); - - global.property(QLatin1String("installer")).setProperty(QLatin1String("components"), - proxy.property(QLatin1String("components"))); - global.property(QLatin1String("installer")).setProperty(QLatin1String("componentByName"), - proxy.property(QLatin1String("componentByName"))); } /*! @@ -661,14 +624,19 @@ QJSValue ScriptEngine::generateDesktopServicesObject() SETPROPERTY(desktopServices, PicturesLocation, QStandardPaths) SETPROPERTY(desktopServices, TempLocation, QStandardPaths) SETPROPERTY(desktopServices, HomeLocation, QStandardPaths) - SETPROPERTY(desktopServices, AppDataLocation, QStandardPaths) + SETPROPERTY(desktopServices, AppLocalDataLocation, QStandardPaths) SETPROPERTY(desktopServices, CacheLocation, QStandardPaths) + SETPROPERTY(desktopServices, GenericCacheLocation, QStandardPaths) SETPROPERTY(desktopServices, GenericDataLocation, QStandardPaths) SETPROPERTY(desktopServices, RuntimeLocation, QStandardPaths) SETPROPERTY(desktopServices, ConfigLocation, QStandardPaths) SETPROPERTY(desktopServices, DownloadLocation, QStandardPaths) SETPROPERTY(desktopServices, GenericCacheLocation, QStandardPaths) SETPROPERTY(desktopServices, GenericConfigLocation, QStandardPaths) + SETPROPERTY(desktopServices, AppDataLocation, QStandardPaths) + SETPROPERTY(desktopServices, AppConfigLocation, QStandardPaths) + SETPROPERTY(desktopServices, PublicShareLocation, QStandardPaths) + SETPROPERTY(desktopServices, TemplatesLocation, QStandardPaths) QJSValue object = m_engine.newQObject(new QDesktopServicesProxy(this)); object.setPrototype(desktopServices); // attach the properties diff --git a/src/libs/installer/scriptengine_p.h b/src/libs/installer/scriptengine_p.h index a0936fe75..101d4f303 100644 --- a/src/libs/installer/scriptengine_p.h +++ b/src/libs/installer/scriptengine_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2024 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -43,36 +43,6 @@ class PackageManagerCore; class PackageManagerGui; class ScriptEngine; -class ConsoleProxy : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY(ConsoleProxy) - -public: - ConsoleProxy() {} - -public slots : - void log(const QString &log) { qCDebug(QInstaller::lcInstallerInstallLog).noquote() << log; } -}; - -class InstallerProxy : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY(InstallerProxy) - -public: - InstallerProxy(ScriptEngine *engine, PackageManagerCore *core) - : m_engine(engine), m_core(core) {} - -public slots: - QJSValue components(const QString ®exp = QString()) const; - QJSValue componentByName(const QString &componentName); - -private: - ScriptEngine *m_engine; - PackageManagerCore *m_core; -}; - class QFileDialogProxy : public QObject { Q_OBJECT @@ -146,6 +116,7 @@ public: Q_INVOKABLE void clickButton(int wizardButton, int delayInMs = 0); Q_INVOKABLE void clickButton(const QString &objectName, int delayInMs = 0) const; Q_INVOKABLE bool isButtonEnabled(int wizardButton); + Q_INVOKABLE void setWizardPageButtonText(int pageId, int buttonId, const QString &buttonText); Q_INVOKABLE void showSettingsButton(bool show); Q_INVOKABLE void setSettingsButtonEnabled(bool enable); @@ -178,8 +149,6 @@ private: } // namespace QInstaller -Q_DECLARE_METATYPE(QInstaller::ConsoleProxy*) -Q_DECLARE_METATYPE(QInstaller::InstallerProxy*) Q_DECLARE_METATYPE(QInstaller::QFileDialogProxy*) Q_DECLARE_METATYPE(QInstaller::QDesktopServicesProxy*) diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp index 85ab0dece..e18f63689 100644 --- a/src/libs/installer/settings.cpp +++ b/src/libs/installer/settings.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -318,13 +318,13 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix, elementList << scName << scVersion << scTitle << scPublisher << scProductUrl << scTargetDir << scAdminTargetDir << scInstallerApplicationIcon << scInstallerWindowIcon - << scLogo << scWatermark << scBanner << scBackground << scPageListPixmap + << scLogo << scWatermark << scBanner << scBackground << scPageListPixmap << scAliasDefinitionsFile << scStartMenuDir << scMaintenanceToolName << scMaintenanceToolIniFile << scMaintenanceToolAlias << scRemoveTargetDir << scLocalCacheDir << scPersistentLocalCache << scRunProgram << scRunProgramArguments << scRunProgramDescription << scDependsOnLocalInstallerBinary - << scAllowSpaceInPath << scAllowNonAsciiCharacters << scDisableAuthorizationFallback - << scDisableCommandLineInterface + << scAllowSpaceInPath << scAllowNonAsciiCharacters << scAllowRepositoriesForOfflineInstaller + << scDisableAuthorizationFallback << scDisableCommandLineInterface << scWizardStyle << scStyleSheet << scTitleColor << scWizardDefaultWidth << scWizardDefaultHeight << scWizardMinimumWidth << scWizardMinimumHeight << scWizardShowPageList << scProductImages @@ -485,11 +485,11 @@ static int lengthToInt(const QVariant &variant) QString length = variant.toString().trimmed(); if (length.endsWith(QLatin1String("em"), Qt::CaseInsensitive)) { length.chop(2); - return qRound(length.toDouble() * QApplication::fontMetrics().height()); + return qRound(length.toDouble() * QFontMetricsF(QApplication::font()).height()); } if (length.endsWith(QLatin1String("ex"), Qt::CaseInsensitive)) { length.chop(2); - return qRound(length.toDouble() * QApplication::fontMetrics().xHeight()); + return qRound(length.toDouble() * QFontMetricsF(QApplication::font()).xHeight()); } if (length.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) { length.chop(2); @@ -539,6 +539,11 @@ void Settings::setProductImages(const QMap<QString, QVariant> &images) d->m_data.insert(scProductImages, QVariant::fromValue(images)); } +QString Settings::aliasDefinitionsFile() const +{ + return d->absolutePathFromKey(scAliasDefinitionsFile); +} + QString Settings::installerApplicationIcon() const { return d->absolutePathFromKey(scInstallerApplicationIcon, systemIconSuffix()); @@ -644,6 +649,11 @@ bool Settings::allowNonAsciiCharacters() const return d->m_data.value(scAllowNonAsciiCharacters, false).toBool(); } +bool Settings::allowRepositoriesForOfflineInstaller() const +{ + return d->m_data.value(scAllowRepositoriesForOfflineInstaller, true).toBool(); +} + bool Settings::disableAuthorizationFallback() const { return d->m_data.value(scDisableAuthorizationFallback, false).toBool(); diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h index f98319110..85b59869c 100644 --- a/src/libs/installer/settings.h +++ b/src/libs/installer/settings.h @@ -95,6 +95,8 @@ public: QMap<QString, QVariant> productImages() const; void setProductImages(const QMap<QString, QVariant> &images); + QString aliasDefinitionsFile() const; + QString applicationName() const; QString version() const; @@ -143,6 +145,8 @@ public: bool allowSpaceInPath() const; bool allowNonAsciiCharacters() const; + bool allowRepositoriesForOfflineInstaller() const; + bool disableAuthorizationFallback() const; bool disableCommandLineInterface() const; diff --git a/src/libs/installer/simplemovefileoperation.cpp b/src/libs/installer/simplemovefileoperation.cpp index 5f3000be0..5bbbdabb4 100644 --- a/src/libs/installer/simplemovefileoperation.cpp +++ b/src/libs/installer/simplemovefileoperation.cpp @@ -93,7 +93,7 @@ bool SimpleMoveFileOperation::performOperation() bool SimpleMoveFileOperation::undoOperation() { - if (parseUndoOperationArguments().count() > 0) + if (skipUndoOperation()) return true; const QString source = arguments().at(0); const QString target = arguments().at(1); diff --git a/src/libs/installer/sysinfo_win.cpp b/src/libs/installer/sysinfo_win.cpp index e74eb1d4e..e5df8e35f 100644 --- a/src/libs/installer/sysinfo_win.cpp +++ b/src/libs/installer/sysinfo_win.cpp @@ -64,7 +64,7 @@ VolumeInfo updateVolumeSizeInformation(const VolumeInfo &info) return update; } -/*! +/* Returns a list of volume info objects that are mounted as network drive shares. */ QList<VolumeInfo> networkVolumeInfosFromMountPoints() @@ -95,7 +95,7 @@ QList<VolumeInfo> networkVolumeInfosFromMountPoints() return volumes; } -/*! +/* Returns a list of volume info objects based on the given \a volumeGUID. The function also solves mounted volume folder paths. It does not return any network drive shares. */ diff --git a/src/libs/installer/systeminfo.cpp b/src/libs/installer/systeminfo.cpp index 6a1976c4a..aed02c569 100644 --- a/src/libs/installer/systeminfo.cpp +++ b/src/libs/installer/systeminfo.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -55,6 +55,7 @@ SystemInfo::SystemInfo(QObject *parent) : QObject(parent) \list \li "i386" \li "x86_64" + \li "arm64" \endlist \note This function depends on what the OS will report and may not detect the actual CPU @@ -62,7 +63,7 @@ SystemInfo::SystemInfo(QObject *parent) : QObject(parent) OS running on a 64-bit CPU is usually unable to determine whether the CPU is actually capable of running 64-bit programs. - \sa QSysInfo::currentCpuArchitecture() + \sa QSysInfo::currentCpuArchitecture() buildCpuArchitecture() */ QString SystemInfo::currentCpuArchitecture() const { @@ -70,6 +71,29 @@ QString SystemInfo::currentCpuArchitecture() const } /*! + \property SystemInfo::buildCpuArchitecture + + The architecture of the CPU that the application was compiled for, in text format. + + Possible values include: + \list + \li "i386" + \li "x86_64" + \li "arm64" + \endlist + + \note Note that this may not match the actual CPU that the application is running on if + there's an emulation layer or if the CPU supports multiple architectures (like x86-64 + processors supporting i386 applications). To detect that, use \c installer.currentCpuArchitecture() + + \sa QSysInfo::buildCpuArchitecture() currentCpuArchitecture() +*/ +QString SystemInfo::buildCpuArchitecture() const +{ + return QSysInfo::buildCpuArchitecture(); +} + +/*! \property SystemInfo::kernelType The type of the operating system kernel the installer was compiled for. It is also the @@ -124,7 +148,7 @@ QString SystemInfo::kernelVersion() const \list \li "windows" \li "opensuse" (for the Linux openSUSE distribution) - \li "osx" + \li "macos" \endlist \sa QSysInfo::productType() diff --git a/src/libs/installer/systeminfo.h b/src/libs/installer/systeminfo.h index c5451605e..a1393397a 100644 --- a/src/libs/installer/systeminfo.h +++ b/src/libs/installer/systeminfo.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -39,6 +39,7 @@ class SystemInfo : public QObject Q_DISABLE_COPY(SystemInfo) Q_PROPERTY(QString currentCpuArchitecture READ currentCpuArchitecture CONSTANT) + Q_PROPERTY(QString buildCpuArchitecture READ buildCpuArchitecture CONSTANT) Q_PROPERTY(QString kernelType READ kernelType CONSTANT) Q_PROPERTY(QString kernelVersion READ kernelVersion CONSTANT) Q_PROPERTY(QString productType READ productType CONSTANT) @@ -49,7 +50,7 @@ public: explicit SystemInfo(QObject *parent = 0); QString currentCpuArchitecture() const; - + QString buildCpuArchitecture() const; QString kernelType() const; QString kernelVersion() const; QString productType() const; diff --git a/src/libs/installer/utils.cpp b/src/libs/installer/utils.cpp index 154a66553..9b64ade37 100644 --- a/src/libs/installer/utils.cpp +++ b/src/libs/installer/utils.cpp @@ -171,7 +171,7 @@ QByteArray QInstaller::calculateHash(QIODevice *device, QCryptographicHash::Algo const qint64 numRead = device->read(buffer.data(), buffer.size()); if (numRead <= 0) return hash.result(); - hash.addData(buffer.constData(), numRead); + hash.addData(buffer.left(numRead)); } return QByteArray(); // never reached } |