diff options
Diffstat (limited to 'src/libs/installer/component.cpp')
-rw-r--r-- | src/libs/installer/component.cpp | 480 |
1 files changed, 313 insertions, 167 deletions
diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp index 66f333377..ce76a2927 100644 --- a/src/libs/installer/component.cpp +++ b/src/libs/installer/component.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. @@ -31,22 +31,27 @@ #include "errors.h" #include "fileutils.h" #include "globals.h" -#include "lib7z_facade.h" +#include "archivefactory.h" #include "messageboxhandler.h" #include "packagemanagercore.h" #include "remoteclient.h" #include "settings.h" #include "utils.h" +#include "constants.h" #include "updateoperationfactory.h" #include <productkeycheck.h> #include <QtCore/QDirIterator> -#include <QtCore/QRegExp> #include <QtCore/QTranslator> +#include <QtCore/QRegularExpression> +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include <QtCore/QTextCodec> +#endif #include <QApplication> +#include <QtConcurrentFilter> #include <QtUiTools/QUiLoader> @@ -59,20 +64,14 @@ #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; -static const QLatin1String scScriptTag("Script"); -static const QLatin1String scVirtual("Virtual"); -static const QLatin1String scInstalled("Installed"); -static const QLatin1String scUpdateText("UpdateText"); -static const QLatin1String scUninstalled("Uninstalled"); -static const QLatin1String scCurrentState("CurrentState"); -static const QLatin1String scForcedInstallation("ForcedInstallation"); -static const QLatin1String scCheckable("Checkable"); -static const QLatin1String scExpandedByDefault("ExpandedByDefault"); -static const QLatin1String scUnstable("Unstable"); - /*! \enum QInstaller::Component::UnstableError @@ -86,6 +85,10 @@ static const QLatin1String scUnstable("Unstable"); Component script has errors or loading fails. \value MissingDependency Component has dependencies to missing components. + \value InvalidTreeName + Component has an invalid tree name. + \value DescendantOfUnstable + Component is descendant of an unstable component. */ /*! @@ -247,8 +250,13 @@ static const QLatin1String scUnstable("Unstable"); */ Component::Component(PackageManagerCore *core) : d(new ComponentPrivate(core, this)) - , m_defaultArchivePath(QLatin1String("@TargetDir@")) + , 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); @@ -284,27 +292,37 @@ void Component::loadDataFromPackage(const KDUpdater::LocalPackage &package) { setValue(scName, package.name); setValue(scDisplayName, package.title); - setValue(scTreeName, package.treeName); setValue(scDescription, package.description); setValue(scVersion, package.version); setValue(scInheritVersion, package.inheritVersionFrom); setValue(scInstalledVersion, package.version); - setValue(QLatin1String("LastUpdateDate"), package.lastUpdateDate.toString()); - setValue(QLatin1String("InstallDate"), package.installDate.toString()); + setValue(scLastUpdateDate, package.lastUpdateDate.toString()); + setValue(scInstallDate, package.installDate.toString()); setValue(scUncompressedSize, QString::number(package.uncompressedSize)); - setValue(scDependencies, package.dependencies.join(QLatin1String(","))); - setValue(scAutoDependOn, package.autoDependencies.join(QLatin1String(","))); + setValue(scDependencies, package.dependencies.join(scCommaWithSpace)); + setValue(scAutoDependOn, package.autoDependencies.join(scCommaWithSpace)); + setValue(scSortingPriority, QString::number(package.sortingPriority)); setValue(scForcedInstallation, package.forcedInstallation ? scTrue : scFalse); setValue(scVirtual, package.virtualComp ? scTrue : scFalse); setValue(scCurrentState, scInstalled); setValue(scCheckable, package.checkable ? scTrue : scFalse); setValue(scExpandedByDefault, package.expandedByDefault ? scTrue : scFalse); + setValue(scContentSha1, package.contentSha1); + + setValue(scTreeName, package.treeName.first); + d->m_treeNameMoveChildren = package.treeName.second; + + // scDependencies might be updated from repository later, + // keep the local dependencies as well. + setValue(scLocalDependencies, value(scDependencies)); } /*! Sets variables according to the values set in the package.xml file of \a package. Also loads UI files, licenses and translations if they are referenced in the package.xml. + If the \c PackageManagerCore object of this component is run as package viewer, then + only sets the variables without loading referenced files. */ void Component::loadDataFromPackage(const Package &package) { @@ -312,7 +330,6 @@ void Component::loadDataFromPackage(const Package &package) setValue(scName, package.data(scName).toString()); setValue(scDisplayName, package.data(scDisplayName).toString()); - setValue(scTreeName, package.data(scTreeName).toString()); setValue(scDescription, package.data(scDescription).toString()); setValue(scDefault, package.data(scDefault).toString()); setValue(scAutoDependOn, package.data(scAutoDependOn).toString()); @@ -330,8 +347,7 @@ void Component::loadDataFromPackage(const Package &package) setValue(scUpdateText, package.data(scUpdateText).toString()); setValue(scNewComponent, package.data(scNewComponent).toString()); setValue(scRequiresAdminRights, package.data(scRequiresAdminRights).toString()); - - setValue(scScriptTag, package.data(scScriptTag).toString()); + d->m_scriptHash = package.data(scScriptTag).toHash(); setValue(scReplaces, package.data(scReplaces).toString()); setValue(scReleaseDate, package.data(scReleaseDate).toString()); setValue(scCheckable, package.data(scCheckable).toString()); @@ -341,22 +357,31 @@ void Component::loadDataFromPackage(const Package &package) if (PackageManagerCore::noForceInstallation()) forced = scFalse; setValue(scForcedInstallation, forced); + setValue(scContentSha1, package.data(scContentSha1).toString()); + setValue(scCheckSha1CheckSum, package.data(scCheckSha1CheckSum, scTrue).toString().toLower()); + + const auto treeNamePair = package.data(scTreeName).value<QPair<QString, bool>>(); + setValue(scTreeName, treeNamePair.first); + d->m_treeNameMoveChildren = treeNamePair.second; + + if (d->m_core->isPackageViewer()) + return; setLocalTempPath(QInstaller::pathFromUrl(package.packageSource().url)); - const QStringList uis = package.data(QLatin1String("UserInterfaces")).toString() - .split(QInstaller::commaRegExp(), QString::SkipEmptyParts); - if (!uis.isEmpty()) - loadUserInterfaces(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), uis); + + const QStringList uiList = QInstaller::splitStringWithComma(package.data(scUserInterfaces).toString()); + if (!uiList.isEmpty()) + loadUserInterfaces(QDir(scTwoArgs.arg(localTempPath(), name())), uiList); + #ifndef IFW_DISABLE_TRANSLATIONS - const QStringList qms = package.data(QLatin1String("Translations")).toString() - .split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + const QStringList qms = QInstaller::splitStringWithComma(package.data(scTranslations).toString()); if (!qms.isEmpty()) - loadTranslations(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), qms); + loadTranslations(QDir(scTwoArgs.arg(localTempPath(), name())), qms); #endif - QHash<QString, QVariant> licenseHash = package.data(QLatin1String("Licenses")).toHash(); + QHash<QString, QVariant> licenseHash = package.data(scLicenses).toHash(); if (!licenseHash.isEmpty()) - loadLicenses(QString::fromLatin1("%1/%2/").arg(localTempPath(), name()), licenseHash); - QVariant operationsVariant = package.data(QLatin1String("Operations")); + loadLicenses(scTwoArgs.arg(localTempPath(), name()), licenseHash); + QVariant operationsVariant = package.data(scOperations); if (operationsVariant.canConvert<QList<QPair<QString, QVariant>>>()) m_operationsList = operationsVariant.value<QList<QPair<QString, QVariant>>>(); } @@ -368,16 +393,20 @@ quint64 Component::updateUncompressedSize() { quint64 size = 0; - if (installAction() == ComponentModelHelper::Install - || installAction() == ComponentModelHelper::KeepInstalled) { + const bool installOrKeepInstalled = (installAction() == ComponentModelHelper::Install + || installAction() == ComponentModelHelper::KeepInstalled); + + if (installOrKeepInstalled) size = d->m_vars.value(scUncompressedSize).toLongLong(); - } foreach (Component* comp, d->m_allChildComponents) size += comp->updateUncompressedSize(); setValue(scUncompressedSizeSum, QString::number(size)); - setData(humanReadableSize(size), UncompressedSize); + if (size == 0 && !installOrKeepInstalled) + setData(QVariant(), UncompressedSize); + else + setData(humanReadableSize(size), UncompressedSize); return size; } @@ -413,6 +442,16 @@ QString Component::value(const QString &key, const QString &defaultValue) const } /*! + Removes all the values that have the \a key from the variables set for this component. + Returns the number of values removed which is 1 if the key exists in the variables, + and 0 otherwise. +*/ +int Component::removeValue(const QString &key) +{ + return d->m_vars.remove(key); +} + +/*! Sets the value of the variable with \a key to \a value. \sa {component::setValue}{component.setValue} @@ -428,16 +467,22 @@ void Component::setValue(const QString &key, const QString &value) if (key == scName) d->m_componentName = normalizedValue; - if (key == scCheckable) - this->setCheckable(normalizedValue.toLower() == scTrue); + if (key == scCheckable) // Non-checkable components can still be toggled in updater + this->setCheckable(normalizedValue.toLower() == scTrue || d->m_core->isUpdater()); if (key == scExpandedByDefault) this->setExpandedByDefault(normalizedValue.toLower() == scTrue); if (key == scForcedInstallation) { - if (value == scTrue && !PackageManagerCore::noForceInstallation()) { + if (value == scTrue && !d->m_core->isUpdater() && !PackageManagerCore::noForceInstallation()) { + // Forced installation components can still be toggled in updater or when + // core is set to ignore forced installations. setCheckable(false); setCheckState(Qt::Checked); } } + if (key == scAutoDependOn) + packageManagerCore()->createAutoDependencyHash(name(), d->m_vars[key], normalizedValue); + if (key == scLocalDependencies) + packageManagerCore()->createLocalDependencyHash(name(), normalizedValue); d->m_vars[key] = normalizedValue; emit valueChanged(key, normalizedValue); @@ -541,53 +586,60 @@ QString Component::displayName() const */ QString Component::treeName() const { - return d->m_vars.value(scTreeName, name()); + const QString defaultValue = d->m_vars.value(scAutoTreeName, name()); + return d->m_vars.value(scTreeName, defaultValue); } /*! - Loads the component script into the script engine. + Returns \c true if descendants of this component should have automatically + created tree names in relation to the parent component's modified location, + \c false otherwise. */ -void Component::loadComponentScript() +bool Component::treeNameMoveChildren() const { - const QString script = d->m_vars.value(scScriptTag); - if (!localTempPath().isEmpty() && !script.isEmpty()) - loadComponentScript(QString::fromLatin1("%1/%2/%3").arg(localTempPath(), name(), script)); + return d->m_treeNameMoveChildren; } /*! - Loads the script at \a fileName into the script engine. The installer and all its - components as well as other useful things are being exported into the script. - For more information, see \l{Component Scripting}. + Loads the component script into the script engine. Call this method with + \a postLoad \c true to a list of components that are updated or installed + to improve performance if the amount of components is huge and there are no script + functions that need to be called before the installation starts. +*/ +void Component::loadComponentScript(const bool postLoad) +{ + const QString installScript(!postLoad ? d->m_scriptHash.value(scInstallScript).toString() + : d->m_scriptHash.value(scPostLoadScript).toString()); + + if (!localTempPath().isEmpty() && !installScript.isEmpty()) { + evaluateComponentScript(scThreeArgs.arg(localTempPath(), name() + , installScript), postLoad); + } +} - Throws an error when either the script at \a fileName could not be opened, or QScriptEngine - could not evaluate the script. +/*! + \internal */ -void Component::loadComponentScript(const QString &fileName) +void Component::evaluateComponentScript(const QString &fileName, const bool postScriptContent) { // introduce the component object as javascript value and call the name to check that it // was successful try { - d->m_scriptContext = d->scriptEngine()->loadInContext(QLatin1String("Component"), fileName, - QString::fromLatin1("var component = installer.componentByName('%1'); component.name;") - .arg(name())); - if (packageManagerCore()->settings().allowUnstableComponents()) { - // Check if component has dependency to a broken component. Dependencies to broken - // components are checked if error is thrown but if dependency to a broken - // component is added in script, the script might not be loaded yet - foreach (QString dependency, dependencies()) { - Component *dependencyComponent = packageManagerCore()->componentByName - (PackageManagerCore::checkableName(dependency)); - if (dependencyComponent && dependencyComponent->isUnstable()) - setUnstable(Component::UnstableError::DepencyToUnstable, QLatin1String("Dependent on unstable component")); - } + if (postScriptContent) { + d->m_postScriptContext = d->scriptEngine()->loadInContext(scComponent, fileName, + scComponentScriptTest.arg(name())); + } else { + d->m_scriptContext = d->scriptEngine()->loadInContext(scComponent, fileName, + scComponentScriptTest.arg(name())); } } catch (const Error &error) { - if (packageManagerCore()->settings().allowUnstableComponents()) { - setUnstable(Component::UnstableError::ScriptLoadingFailed, error.message()); - qCWarning(QInstaller::lcDeveloperBuild) << error.message(); - } else { + qCWarning(QInstaller::lcDeveloperBuild) << error.message(); + setUnstable(Component::UnstableError::ScriptLoadingFailed, error.message()); + // evaluateComponentScript is called with postScriptContent after we have selected components + // and are about to install. Do not allow install if unstable components are allowed + // as we then end up installing a component which has invalid script. + if (!packageManagerCore()->settings().allowUnstableComponents() || postScriptContent) throw error; - } } emit loaded(); @@ -601,7 +653,7 @@ void Component::loadComponentScript(const QString &fileName) */ void Component::languageChanged() { - d->scriptEngine()->callScriptMethod(d->m_scriptContext, QLatin1String("retranslateUi")); + callScriptMethod(scRetranslateUi); } /*! @@ -613,26 +665,26 @@ void Component::loadTranslations(const QDir &directory, const QStringList &qms) { QDirIterator it(directory.path(), qms, QDir::Files); const QStringList translations = d->m_core->settings().translations(); - const QString uiLanguage = QLocale().uiLanguages().value(0, QLatin1String("en")); + const QString uiLanguage = QLocale().uiLanguages().value(0, scEn); while (it.hasNext()) { const QString filename = it.next(); const QString basename = QFileInfo(filename).baseName(); - if (!uiLanguage.startsWith(QFileInfo(filename).baseName(), Qt::CaseInsensitive)) - continue; // do not load the file if it does not match the UI language if (!translations.isEmpty()) { bool found = false; foreach (const QString &translation, translations) - found |= translation.startsWith(basename, Qt::CaseInsensitive); + found |= translation.startsWith(scIfw_ + basename, Qt::CaseInsensitive); if (!found) // don't load the file if it does match the UI language but is not allowed to be used continue; + } else if (!uiLanguage.startsWith(QFileInfo(filename).baseName(), Qt::CaseInsensitive)) { + 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()); } } } @@ -650,17 +702,17 @@ void Component::loadUserInterfaces(const QDir &directory, const QStringList &uis while (it.hasNext()) { QFile file(it.next()); if (!file.open(QIODevice::ReadOnly)) { - throw Error(tr("Cannot open the requested UI file \"%1\": %2").arg( - it.fileName(), file.errorString())); + throw Error(tr("Cannot open the requested UI file \"%1\": %2.\n\n%3 \"%4\"").arg( + 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").arg( - it.fileName(), loader.errorString())); + 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())); } d->scriptEngine()->newQObject(widget); d->m_userInterfaces.insert(widget->objectName(), widget); @@ -676,7 +728,7 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar QHash<QString, QVariant>::const_iterator it; for (it = licenseHash.begin(); it != licenseHash.end(); ++it) { QVariantMap license = it.value().toMap(); - const QString &fileName = license.value(QLatin1String("file")).toString(); + const QString &fileName = license.value(scFile).toString(); if (!ProductKeyCheck::instance()->isValidLicenseTextFile(fileName)) continue; @@ -687,8 +739,8 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar break; QList<QFileInfo> fileCandidates; - foreach (const QString &locale, QInstaller::localeCandidates(lang.toLower())) { - fileCandidates << QFileInfo(QString::fromLatin1("%1%2_%3.%4").arg( + foreach (const QString &locale, QInstaller::localeCandidates(lang)) { + fileCandidates << QFileInfo(scLocalesArgs.arg( directory, fileInfo.baseName(), locale, fileInfo.completeSuffix())); } @@ -705,12 +757,14 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar QFile file(fileInfo.filePath()); if (!file.open(QIODevice::ReadOnly)) { - throw Error(tr("Cannot open the requested license file \"%1\": %2").arg( - file.fileName(), file.errorString())); + throw Error(tr("Cannot open the requested license file \"%1\": %2.\n\n%3 \"%4\"").arg( + 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"); - license.insert(QLatin1String("content"), stream.readAll()); +#endif + license.insert(scContent, stream.readAll()); d->m_licenses.insert(it.key(), license); } } @@ -721,8 +775,8 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar */ void Component::loadXMLOperations() { - for (auto operation: m_operationsList) { - if (operation.first != QLatin1String("Extract")) + for (auto operation: qAsConst(m_operationsList)) { + if (operation.first != scExtract) addOperation(operation.first, operation.second.toStringList()); } } @@ -733,14 +787,14 @@ void Component::loadXMLOperations() */ void Component::loadXMLExtractOperations() { - for (auto operation: m_operationsList) { - if (operation.first == QLatin1String("Extract")) { + for (auto &operation: qAsConst(m_operationsList)) { + if (operation.first == scExtract) { // Create hash for Extract operations. Operation has a mandatory extract folder as // first argument and optional archive name as second argument. const QStringList &operationArgs = operation.second.toStringList(); if (operationArgs.count() == 2) { const QString archiveName = value(scVersion) + operationArgs.at(1); - const QString archivePath = QString::fromLatin1("installer://%1/%2").arg(name()).arg(archiveName); + const QString archivePath = scInstallerPrefixWithTwoArgs.arg(name()).arg(archiveName); m_archivesHash.insert(archivePath, operationArgs.at(0)); } else if (operationArgs.count() == 1) { m_defaultArchivePath = operationArgs.at(0); @@ -798,25 +852,23 @@ void Component::createOperationsForPath(const QString &path) const QFileInfo fi(path); // don't copy over a checksum file - if (fi.suffix() == QLatin1String("sha1") && QFileInfo(fi.dir(), fi.completeBaseName()).exists()) + if (fi.suffix() == scSha1 && QFileInfo(fi.dir(), fi.completeBaseName()).exists()) return; // the script can override this method - if (!d->scriptEngine()->callScriptMethod(d->m_scriptContext, - QLatin1String("createOperationsForPath"), QJSValueList() << path).isUndefined()) { - return; - } + if (!callScriptMethod(scCreateOperationsForPath, QJSValueList() << path).isUndefined()) + return; QString target; - static const QString prefix = QString::fromLatin1("installer://"); - target = QString::fromLatin1("@TargetDir@%1").arg(path.mid(prefix.length() + name().length())); + static const QString prefix = scInstallerPrefix; + target = scTargetDirPlaceholderWithArg.arg(path.mid(prefix.length() + name().length())); if (fi.isFile()) { - static const QString copy = QString::fromLatin1("Copy"); + static const QString copy = scCopy; addOperation(copy, QStringList() << fi.filePath() << target); } else if (fi.isDir()) { qApp->processEvents(); - static const QString mkdir = QString::fromLatin1("Mkdir"); + static const QString mkdir = scMkdir; addOperation(mkdir, QStringList(target)); QDirIterator it(fi.filePath()); @@ -844,23 +896,22 @@ void Component::createOperationsForArchive(const QString &archive) const QFileInfo fi(archive); // don't do anything with sha1 files - if (fi.suffix() == QLatin1String("sha1") && QFileInfo(fi.dir(), fi.completeBaseName()).exists()) + if (fi.suffix() == scSha1 && QFileInfo(fi.dir(), fi.completeBaseName()).exists()) return; // the script can override this method - if (!d->scriptEngine()->callScriptMethod(d->m_scriptContext, - QLatin1String("createOperationsForArchive"), QJSValueList() << archive).isUndefined()) { - return; - } + if (!callScriptMethod(scCreateOperationsForArchive, QJSValueList() << archive).isUndefined()) + return; - const bool isZip = Lib7z::isSupportedArchive(archive); + QScopedPointer<AbstractArchive> archiveFile(ArchiveFactory::instance().create(archive)); + const bool isZip = (archiveFile && archiveFile->open(QIODevice::ReadOnly) && archiveFile->isSupported()); if (isZip) { // component.xml can override this value if (m_archivesHash.contains(archive)) - addOperation(QLatin1String("Extract"), QStringList() << archive << m_archivesHash.value(archive)); + addOperation(scExtract, QStringList() << archive << m_archivesHash.value(archive)); else - addOperation(QLatin1String("Extract"), QStringList() << archive << m_defaultArchivePath); + addOperation(scExtract, QStringList() << archive << m_defaultArchivePath); } else { createOperationsForPath(archive); } @@ -872,7 +923,7 @@ void Component::createOperationsForArchive(const QString &archive) void Component::beginInstallation() { // the script can override this method - d->scriptEngine()->callScriptMethod(d->m_scriptContext, QLatin1String("beginInstallation")); + callScriptMethod(scBeginInstallation); } /*! @@ -882,10 +933,9 @@ void Component::beginInstallation() void Component::createOperations() { // the script can override this method - if (!d->scriptEngine()->callScriptMethod(d->m_scriptContext, QLatin1String("createOperations")) - .isUndefined()) { - d->m_operationsCreated = true; - return; + if (!callScriptMethod(scCreateOperations).isUndefined()) { + d->m_operationsCreated = true; + return; } loadXMLExtractOperations(); foreach (const QString &archive, archives()) @@ -922,10 +972,17 @@ QList<QPair<QString, bool> > Component::pathsForUninstallation() const */ QStringList Component::archives() const { - QString pathString = QString::fromLatin1("installer://%1/").arg(name()); + 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(QRegExp(QLatin1String("^")), pathString); + archivesNameList.replaceInStrings(regExp, pathString); return archivesNameList; } @@ -945,6 +1002,15 @@ void Component::addDownloadableArchive(const QString &path) } /*! + \internal +*/ +void Component::addDownloadableArchives(const QString& archives) +{ + Q_ASSERT(isFromOnlineRepository()); + d->m_downloadableArchivesVariable = archives; +} + +/*! Removes the archive \a path previously added via addDownloadableArchive() from this component. This can only be called if this component was downloaded from an online repository. @@ -959,9 +1025,15 @@ void Component::removeDownloadableArchive(const QString &path) /*! Returns the archives to be downloaded from the online repository before installation. + Should be called only once when the installation starts. */ -QStringList Component::downloadableArchives() const +QStringList Component::downloadableArchives() { + const QStringList downloadableArchives = d->m_downloadableArchivesVariable + .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); + foreach (const QString downloadableArchive, downloadableArchives) + addDownloadableArchive(downloadableArchive); + return d->m_downloadableArchives; } @@ -1012,35 +1084,43 @@ QStringList Component::stopProcessForUpdateRequests() const /*! Returns the operations needed to install this component. If autoCreateOperations() is \c true, createOperations() is called if no operations have been automatically created yet. + + The \a mask parameter filters the returned operations by their group. */ -OperationList Component::operations() const +OperationList Component::operations(const Operation::OperationGroups &mask) const { if (d->m_autoCreateOperations && !d->m_operationsCreated) { const_cast<Component*>(this)->createOperations(); if (!d->m_minimumProgressOperation) { d->m_minimumProgressOperation = KDUpdater::UpdateOperationFactory::instance() - .create(QLatin1String("MinimumProgress"), d->m_core); - d->m_minimumProgressOperation->setValue(QLatin1String("component"), name()); + .create(scMinimumProgress, d->m_core); + d->m_minimumProgressOperation->setValue(scComponentSmall, name()); d->m_operations.append(d->m_minimumProgressOperation); } if (!d->m_licenses.isEmpty()) { d->m_licenseOperation = KDUpdater::UpdateOperationFactory::instance() - .create(QLatin1String("License"), d->m_core); - d->m_licenseOperation->setValue(QLatin1String("component"), name()); + .create(scLicense, d->m_core); + d->m_licenseOperation->setValue(scComponentSmall, name()); QVariantMap licenses; const QList<QVariantMap> values = d->m_licenses.values(); for (int i = 0; i < values.count(); ++i) { - licenses.insert(values.at(i).value(QLatin1String("file")).toString(), - values.at(i).value(QLatin1String("content"))); + licenses.insert(values.at(i).value(scFile).toString(), + values.at(i).value(scContent)); } - d->m_licenseOperation->setValue(QLatin1String("licenses"), licenses); + d->m_licenseOperation->setValue(scLicensesValue, licenses); d->m_operations.append(d->m_licenseOperation); } } - return d->m_operations; + OperationList operations; + std::copy_if(d->m_operations.begin(), d->m_operations.end(), std::back_inserter(operations), + [&](const Operation *op) { + return mask.testFlag(op->group()); + } + ); + return operations; } /*! @@ -1050,7 +1130,7 @@ void Component::addOperation(Operation *operation) { d->m_operations.append(operation); if (RemoteClient::instance().isActive()) - operation->setValue(QLatin1String("admin"), true); + operation->setValue(scAdmin, true); } /*! @@ -1060,7 +1140,7 @@ void Component::addOperation(Operation *operation) void Component::addElevatedOperation(Operation *operation) { addOperation(operation); - operation->setValue(QLatin1String("admin"), true); + operation->setValue(scAdmin, true); } /*! @@ -1115,9 +1195,6 @@ Operation *Component::createOperation(const QString &operationName, const QStrin return operation; } - if (operation->name() == QLatin1String("Delete")) - operation->setValue(QLatin1String("performUndo"), false); - // Operation can contain variables which are resolved when performing the operation if (operation->requiresUnreplacedVariables()) operation->setArguments(parameters); @@ -1125,11 +1202,11 @@ Operation *Component::createOperation(const QString &operationName, const QStrin operation->setArguments(d->m_core->replaceVariables(parameters)); - operation->setValue(QLatin1String("component"), name()); + operation->setValue(scComponentSmall, name()); return operation; } -void Component::markComponentUnstable() +void Component::markComponentUnstable(Component::UnstableError error, const QString &errorMessage) { setValue(scDefault, scFalse); // Mark unstable component unchecked if: @@ -1142,6 +1219,23 @@ void Component::markComponentUnstable() if (d->m_core->isInstaller() || !isInstalled() || d->m_core->isUpdater()) setCheckState(Qt::Unchecked); setValue(scUnstable, scTrue); + QMetaEnum metaEnum = QMetaEnum::fromType<Component::UnstableError>(); + emit packageManagerCore()->unstableComponentFound(QLatin1String(metaEnum.valueToKey(error)), errorMessage, this->name()); + + // Update the description and tooltip texts to contain + // information about the unstable error. + updateModelData(scDescription, QString()); +} + +QJSValue Component::callScriptMethod(const QString &methodName, const QJSValueList &arguments) const +{ + QJSValue scriptContext; + if (!d->m_postScriptContext.isUndefined() && d->m_postScriptContext.property(methodName).isCallable()) + scriptContext = d->m_postScriptContext; + else + scriptContext = d->m_scriptContext; + return d->scriptEngine()->callScriptMethod(scriptContext, + methodName, arguments); } namespace { @@ -1163,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 { @@ -1278,6 +1372,15 @@ bool Component::forcedInstallation() const } /*! + Returns whether this component is essential. Essential components + are always installed, and updated before other components. +*/ +bool Component::isEssential() const +{ + return d->m_vars.value(scEssential, scFalse).toLower() == scTrue; +} + +/*! Sets the validator callback name to \a name. */ void Component::setValidatorCallbackName(const QString &name) @@ -1292,12 +1395,14 @@ void Component::setValidatorCallbackName(const QString &name) bool Component::validatePage() { if (!validatorCallbackName.isEmpty()) - return d->scriptEngine()->callScriptMethod(d->m_scriptContext, validatorCallbackName).toBool(); + return callScriptMethod(validatorCallbackName).toBool(); return true; } /*! Adds the component specified by \a newDependency to the list of dependencies. + Alternatively, multiple components can be specified by separating each with + a comma. \sa {component::addDependency}{component.addDependency} \sa dependencies @@ -1309,16 +1414,29 @@ void Component::addDependency(const QString &newDependency) if (oldDependencies.isEmpty()) setValue(scDependencies, newDependency); else - setValue(scDependencies, oldDependencies + QLatin1String(", ") + newDependency); + setValue(scDependencies, oldDependencies + scCommaWithSpace + newDependency); } +/*! + Returns a list of dependencies defined in the the repository or in the package.xml. +*/ QStringList Component::dependencies() const { - return d->m_vars.value(scDependencies).split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + return QInstaller::splitStringWithComma(d->m_vars.value(scDependencies)); +} + +/*! + Returns a list of installed components dependencies defined in the components.xml. +*/ +QStringList Component::localDependencies() const +{ + return QInstaller::splitStringWithComma(d->m_vars.value(scLocalDependencies)); } /*! Adds the component specified by \a newDependOn to the automatic depend-on list. + Alternatively, multiple components can be specified by separating each with + a comma. \sa {component::addAutoDependOn}{component.addAutoDependOn} \sa autoDependencies @@ -1330,12 +1448,30 @@ void Component::addAutoDependOn(const QString &newDependOn) if (oldDependOn.isEmpty()) setValue(scAutoDependOn, newDependOn); else - setValue(scAutoDependOn, oldDependOn + QLatin1String(", ") + newDependOn); + setValue(scAutoDependOn, oldDependOn + scCommaWithSpace + newDependOn); } QStringList Component::autoDependencies() const { - return d->m_vars.value(scAutoDependOn).split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + return d->m_vars.value(scAutoDependOn).split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); +} + +/*! + Returns a list of dependencies that the component currently has. The + dependencies can vary when component is already installed with different + dependency list than what is introduced in the repository. If component is + not installed, or update is requested to an installed component, + current dependencies are read from repository so that correct dependencies + are calculated for the component when it is installed or updated. +*/ +QStringList Component::currentDependencies() const +{ + QStringList dependenciesList; + if (isInstalled() && !updateRequested()) + dependenciesList = localDependencies(); + else + dependenciesList = dependencies(); + return dependenciesList; } /*! @@ -1366,7 +1502,7 @@ bool Component::isAutoDependOn(const QSet<QString> &componentsToInstall) const // essential updates needs to be installed first, otherwise non-essential components // will be installed if (packageManagerCore()->foundEssentialUpdate()) { - const QSet<QString> autoDependOnSet = autoDependOnList.toSet(); + const QSet<QString> autoDependOnSet(autoDependOnList.begin(), autoDependOnList.end()); if (componentsToInstall.contains(autoDependOnSet)) { foreach (const QString &autoDep, autoDependOnSet) { Component *component = packageManagerCore()->componentByName(autoDep); @@ -1404,8 +1540,7 @@ bool Component::isDefault() const if (d->m_vars.value(scDefault).compare(scScript, Qt::CaseInsensitive) == 0) { QJSValue valueFromScript; try { - valueFromScript = d->scriptEngine()->callScriptMethod(d->m_scriptContext, - QLatin1String("isDefault")); + valueFromScript = callScriptMethod(scIsDefault); } catch (const Error &error) { MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("isDefaultError"), tr("Cannot resolve isDefault in %1").arg(name()), @@ -1461,11 +1596,21 @@ void Component::setUpdateAvailable(bool isUpdateAvailable) } /*! + Returns whether update is available for this component. + + \sa {component::isUpdateAvailable}{component.isUpdateAvailable} +*/ +bool Component::isUpdateAvailable() const +{ + return d->m_updateIsAvailable && !isUnstable(); +} + +/*! Returns whether the user wants to install the update for this component. \sa {component::updateRequested}{component.updateRequested} */ -bool Component::updateRequested() +bool Component::updateRequested() const { return d->m_updateIsAvailable && isSelected() && !isUnstable(); } @@ -1527,22 +1672,23 @@ void Component::setUnstable(Component::UnstableError error, const QString &error { QList<Component*> dependencies = d->m_core->dependees(this); // Mark this component unstable - markComponentUnstable(); + markComponentUnstable(error, errorMessage); // Marks all components unstable that depend on the unstable component foreach (Component *dependency, dependencies) { - dependency->markComponentUnstable(); + dependency->markComponentUnstable(UnstableError::DepencyToUnstable, + QLatin1String("Dependent on unstable component")); foreach (Component *descendant, dependency->descendantComponents()) { - descendant->markComponentUnstable(); + descendant->markComponentUnstable(UnstableError::DescendantOfUnstable, + QLatin1String("Descendant of unstable component")); } } // Marks all child components unstable foreach (Component *descendant, this->descendantComponents()) { - descendant->markComponentUnstable(); + descendant->markComponentUnstable(UnstableError::DescendantOfUnstable, + QLatin1String("Descendant of unstable component")); } - QMetaEnum metaEnum = QMetaEnum::fromType<Component::UnstableError>(); - emit packageManagerCore()->unstableComponentFound(QLatin1String(metaEnum.valueToKey(error)), errorMessage, this->name()); } /*! @@ -1628,23 +1774,23 @@ void Component::updateModelData(const QString &key, const QString &data) setData(humanReadableSize(size), UncompressedSize); } - const QString &updateInfo = d->m_vars.value(scUpdateText); - if (!d->m_core->isUpdater() || updateInfo.isEmpty()) { - QString tooltipText - = QString::fromLatin1("<html><body>%1</body></html>").arg(d->m_vars.value(scDescription)); - if (isUnstable()) { - tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. " - "This component can not be installed."); + if (key == scUpdateText || key == scDescription) { + QString tooltipText; + const QString &updateInfo = d->m_vars.value(scUpdateText); + if (!d->m_core->isUpdater() || updateInfo.isEmpty()) { + tooltipText = QString::fromLatin1("<html><body>%1</body></html>").arg(d->m_vars.value(scDescription)); + } else { + tooltipText = d->m_vars.value(scDescription) + scBr + scBr + + tr("Update Info: ") + updateInfo; } - setData(tooltipText, Qt::ToolTipRole); - } else { - QString tooltipText - = d->m_vars.value(scDescription) + QLatin1String("<br><br>") - + tr("Update Info: ") + updateInfo; if (isUnstable()) { - tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. " - "This component can not be updated."); + tooltipText += scBr + tr("There was an error loading the selected component. " + "This component cannot be installed."); } + static const QRegularExpression externalLinkRegexp(QLatin1String("{external-link}='(.*?)'")); + static const QLatin1String externalLinkElement(QLatin1String("<a href=\"\\1\">\\1</a>")); + // replace {external-link}='' fields in component description with proper link tags + tooltipText.replace(externalLinkRegexp, externalLinkElement); setData(tooltipText, Qt::ToolTipRole); } |