diff options
Diffstat (limited to 'src/libs/installer/packagemanagercore_p.cpp')
-rw-r--r-- | src/libs/installer/packagemanagercore_p.cpp | 938 |
1 files changed, 616 insertions, 322 deletions
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 85e902ac3..10ca27d00 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.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. @@ -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> @@ -123,54 +126,27 @@ static QStringList checkRunningProcessesFromList(const QStringList &processList) static void deferredRename(const QString &oldName, const QString &newName, bool restart = false) { #ifdef Q_OS_WIN - QStringList arguments; - - // Check if .vbs extension can be used for running renaming script. If not, create own extension - QString extension = QLatin1String(".vbs"); - QSettingsWrapper settingRoot(QLatin1String("HKEY_CLASSES_ROOT\\.vbs"), QSettingsWrapper::NativeFormat); - if (settingRoot.value(QLatin1String(".")).toString() != QLatin1String("VBSFile")) { - extension = QLatin1String(".qtInstaller"); - QSettingsWrapper settingsUser(QLatin1String("HKEY_CURRENT_USER\\Software\\Classes"), QSettingsWrapper::NativeFormat); - QString value = settingsUser.value(extension).toString(); - if (value != QLatin1String("VBSFile")) - settingsUser.setValue(extension, QLatin1String("VBSFile")); - } - QTemporaryFile f(QDir::temp().absoluteFilePath(QLatin1String("deferredrenameXXXXXX%1")).arg(extension)); + const QString currentExecutable = QCoreApplication::applicationFilePath(); + const QString tmpExecutable = generateTemporaryFileName(currentExecutable); - QInstaller::openForWrite(&f); - f.setAutoRemove(false); + QFile::rename(currentExecutable, tmpExecutable); + QFile::rename(oldName, newName); - arguments << QDir::toNativeSeparators(f.fileName()) << QDir::toNativeSeparators(oldName) - << QDir::toNativeSeparators(QFileInfo(oldName).dir().absoluteFilePath(QFileInfo(newName) - .fileName())); - - QTextStream batch(&f); - batch.setCodec("UTF-16"); - batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n"; - batch << "Set tmp = WScript.CreateObject(\"WScript.Shell\")\n"; - batch << QString::fromLatin1("file = \"%1\"\n").arg(arguments[2]); - batch << "on error resume next\n"; - - batch << "while fso.FileExists(file)\n"; - batch << " fso.DeleteFile(file)\n"; - batch << " WScript.Sleep(1000)\n"; - batch << "wend\n"; - batch << QString::fromLatin1("fso.MoveFile \"%1\", file\n").arg(arguments[1]); + QStringList arguments; if (restart) { - //Restart with same command line arguments as first executable - QStringList commandLineArguments = QCoreApplication::arguments(); - batch << QString::fromLatin1("tmp.exec \"%1 --%2") - .arg(arguments[2]).arg(CommandLineOptions::scStartUpdaterLong); - //Skip the first argument as that is executable itself - for (int i = 1; i < commandLineArguments.count(); i++) { - batch << QString::fromLatin1(" %1").arg(commandLineArguments.at(i)); - } - batch << QString::fromLatin1("\"\n"); + // 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); } - batch << "fso.DeleteFile(WScript.ScriptFullName)\n"; + QProcessWrapper::startDetached2(newName, arguments); - QProcessWrapper::startDetached(QLatin1String("cscript"), QStringList() << QLatin1String("//Nologo") - << arguments[0]); #else QFile::remove(newName); QFile::rename(oldName, newName); @@ -178,26 +154,56 @@ static void deferredRename(const QString &oldName, const QString &newName, bool #endif } +static bool filterMissingAliasesToInstall(const QString& component, const QList<ComponentAlias *> packages) +{ + bool packageFound = false; + for (qsizetype i = 0; i < packages.size(); ++i) { + packageFound = (packages.at(i)->name() == component); + if (packageFound) + break; + } + return !packageFound; +} + +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; +} // -- 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_componentsToInstallCalculated(false) + , m_foundEssentialUpdate(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) , m_installerCalculator(nullptr) @@ -205,37 +211,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 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_componentsToInstallCalculated(false) + , m_foundEssentialUpdate(false) , m_componentScriptEngine(nullptr) , m_controlScriptEngine(nullptr) , m_installerCalculator(nullptr) @@ -243,21 +258,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; @@ -268,7 +283,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q << operation.name; continue; } - m_performedOperationsOld.append(op.take()); + m_performedOperationsOld.append(op.release()); } connect(this, &PackageManagerCorePrivate::installationStarted, @@ -297,6 +312,7 @@ PackageManagerCorePrivate::~PackageManagerCorePrivate() qDeleteAll(m_performedOperationsCurrentSession); delete m_updateFinder; + delete m_aliasFinder; delete m_proxyFactory; delete m_defaultModel; @@ -402,11 +418,9 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c // after everything is set up, load the scripts if needed and create helper hashes // for autodependency and dependency components for quicker search later - foreach (QInstaller::Component *component, components) { - if (loadScript) - component->loadComponentScript(); - createDependencyHashes(component); - } + if (loadScript && !loadComponentScripts(components)) + return false; + // now we can preselect components in the tree foreach (QInstaller::Component *component, components) { // set the checked state for all components without child (means without tristate) @@ -430,10 +444,10 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c component->setCheckState(Qt::Checked); clearInstallerCalculator(); - if (installerCalculator()->appendComponentsToInstall(components.values()) == false) { - setStatus(PackageManagerCore::Failure, installerCalculator()->componentsToInstallError()); + if (installerCalculator()->solve(components.values()) == false) { + setStatus(PackageManagerCore::Failure, installerCalculator()->error()); MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), - tr("Unresolved dependencies"), installerCalculator()->componentsToInstallError()); + tr("Unresolved dependencies"), installerCalculator()->error()); return false; } @@ -459,12 +473,120 @@ 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); + } + } + // After aliases are loaded, perform sanity checks: + + // 1. Component check state is changed by alias selection, so store the initial state + storeCheckState(); + + Graph<QString> aliasGraph; + for (auto *alias : qAsConst(m_componentAliases)) { + 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())); + } + } + + const QList<QString> sortedAliases = aliasGraph.sort(); + // 2. 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; + } + + // 3. 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(); + // 4. Check for other errors preventing resolving components to install + if (!installerCalculator()->solve(m_componentAliases.values())) { + setStatus(PackageManagerCore::Failure, installerCalculator()->error()); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), + tr("Unresolved component aliases"), installerCalculator()->error()); + + return false; + } + + for (auto *alias : qAsConst(m_componentAliases)) + alias->setSelected(false); + + // 5. Restore original state + restoreCheckState(); + + return true; +} + +template <typename T> +bool PackageManagerCorePrivate::loadComponentScripts(const T &components, const bool postScript) +{ + infoMessage(nullptr, tr("Loading component scripts...")); + + quint64 loadedComponents = 0; + for (auto *component : components) { + if (statusCanceledOrFailed()) + return false; + + component->loadComponentScript(postScript); + ++loadedComponents; + + const int currentProgress = qRound(double(loadedComponents) / components.count() * 100); + infoProgress(nullptr, currentProgress, 100); + qApp->processEvents(); + } + return true; +} + +template bool PackageManagerCorePrivate::loadComponentScripts<QList<Component *>>(const QList<Component *> &, const bool); +template bool PackageManagerCorePrivate::loadComponentScripts<QHash<QString, Component *>>(const QHash<QString, Component *> &, const bool); + void PackageManagerCorePrivate::cleanUpComponentEnvironment() { m_componentReplaces.clear(); m_autoDependencyComponentHash.clear(); - m_dependencyComponentHash.clear(); + m_localDependencyComponentHash.clear(); m_localVirtualComponents.clear(); + m_componentByNameHash.clear(); // clean up registered (downloaded) data if (m_core->isMaintainer()) BinaryFormatEngineHandler::instance()->clear(); @@ -473,6 +595,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 @@ -491,6 +617,9 @@ ScriptEngine *PackageManagerCorePrivate::controlScriptEngine() const void PackageManagerCorePrivate::clearAllComponentLists() { + qDeleteAll(m_componentAliases); + m_componentAliases.clear(); + QList<QInstaller::Component*> toDelete; toDelete << m_rootComponents << m_deletedReplacedComponents; @@ -499,7 +628,7 @@ void PackageManagerCorePrivate::clearAllComponentLists() m_deletedReplacedComponents.clear(); m_componentsToReplaceAllMode.clear(); - m_componentsToInstallCalculated = false; + m_foundEssentialUpdate = false; qDeleteAll(toDelete); cleanUpComponentEnvironment(); @@ -507,8 +636,10 @@ void PackageManagerCorePrivate::clearAllComponentLists() void PackageManagerCorePrivate::clearUpdaterComponentLists() { - QSet<Component*> usedComponents = - QSet<Component*>::fromList(m_updaterComponents + m_updaterComponentsDeps); + + QSet<Component*> usedComponents(m_updaterComponents.begin(), m_updaterComponents.end()); + usedComponents.unite(QSet<Component*>(m_updaterComponentsDeps.begin(), + m_updaterComponentsDeps.end())); const QList<QPair<Component*, Component*> > list = m_componentsToReplaceUpdaterMode.values(); for (int i = 0; i < list.count(); ++i) { @@ -524,7 +655,7 @@ void PackageManagerCorePrivate::clearUpdaterComponentLists() m_updaterDependencyReplacements.clear(); m_componentsToReplaceUpdaterMode.clear(); - m_componentsToInstallCalculated = false; + m_foundEssentialUpdate = false; qDeleteAll(usedComponents); cleanUpComponentEnvironment(); @@ -545,21 +676,6 @@ QHash<QString, QStringList> &PackageManagerCorePrivate::componentReplaces() return m_componentReplaces; } -QList<Component*> PackageManagerCorePrivate::replacedComponentsByName(const QString &name) -{ - // Creates a list of components which are replaced by component 'name' - QList<Component*> replacedComponents; - if (m_componentReplaces.contains(name)) { - for (const QString &replacedComponentName : m_componentReplaces.value(name)) { - Component *replacedComponent = m_core->componentByName(replacedComponentName, - m_core->components(PackageManagerCore::ComponentType::All)); - if (replacedComponent) - replacedComponents.append(replacedComponent); - } - } - return replacedComponents; -} - void PackageManagerCorePrivate::clearInstallerCalculator() { delete m_installerCalculator; @@ -570,8 +686,7 @@ InstallerCalculator *PackageManagerCorePrivate::installerCalculator() const { if (!m_installerCalculator) { PackageManagerCorePrivate *const pmcp = const_cast<PackageManagerCorePrivate *> (this); - pmcp->m_installerCalculator = new InstallerCalculator(m_core, - m_core->components(PackageManagerCore::ComponentType::AllNoReplacements), pmcp->m_autoDependencyComponentHash); + pmcp->m_installerCalculator = new InstallerCalculator(m_core, pmcp->m_autoDependencyComponentHash); } return m_installerCalculator; } @@ -595,8 +710,8 @@ UninstallerCalculator *PackageManagerCorePrivate::uninstallerCalculator() const } } - pmcp->m_uninstallerCalculator = new UninstallerCalculator(installedComponents, m_core, - pmcp->m_autoDependencyComponentHash, pmcp->m_dependencyComponentHash, pmcp->m_localVirtualComponents); + pmcp->m_uninstallerCalculator = new UninstallerCalculator(m_core, + pmcp->m_autoDependencyComponentHash, pmcp->m_localDependencyComponentHash, pmcp->m_localVirtualComponents); } return m_uninstallerCalculator; } @@ -605,7 +720,6 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> ¶ms { m_coreCheckedHash.clear(); m_data = PackageManagerCoreData(params, isInstaller()); - m_componentsToInstallCalculated = false; #ifdef Q_OS_LINUX if (m_launchedAsRoot && isInstaller()) @@ -663,11 +777,16 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> ¶ms m_localPackageHub->setApplicationVersion(QLatin1String(QUOTE(IFW_REPOSITORY_FORMAT_VERSION))); if (isInstaller()) - m_packageSources.insert(PackageSource(QUrl(QLatin1String("resource://metadata/")), 0)); + 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); + connect(&m_metadataJob, &Job::infoMessage, this, &PackageManagerCorePrivate::infoMessage); connect(&m_metadataJob, &Job::progress, this, &PackageManagerCorePrivate::infoProgress); connect(&m_metadataJob, &Job::totalProgress, this, &PackageManagerCorePrivate::totalProgress); @@ -770,7 +889,12 @@ Operation *PackageManagerCorePrivate::takeOwnedOperation(Operation *operation) QString PackageManagerCorePrivate::maintenanceToolName() const { - QString filename = m_data.settings().maintenanceToolName(); + QString filename; + if (isInstaller()) + filename = m_data.settings().maintenanceToolName(); + else + filename = QCoreApplication::applicationName(); + #if defined(Q_OS_MACOS) if (QInstaller::isInBundle(QCoreApplication::applicationDirPath())) filename += QLatin1String(".app/Contents/MacOS/") + filename; @@ -783,12 +907,16 @@ QString PackageManagerCorePrivate::maintenanceToolName() const QString PackageManagerCorePrivate::maintenanceToolAliasPath() const { #ifdef Q_OS_MACOS - const bool isRoot = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()); + const QString aliasName = m_data.settings().maintenanceToolAlias(); + if (aliasName.isEmpty()) + return QString(); + + const bool isRoot = m_core->hasAdminRights(); const QString applicationsDir = m_core->value( isRoot ? QLatin1String("ApplicationsDir") : QLatin1String("ApplicationsDirUser") ); QString maintenanceToolAlias = QString::fromLatin1("%1/%2") - .arg(applicationsDir, m_data.settings().maintenanceToolAlias()); + .arg(applicationsDir, aliasName); if (!maintenanceToolAlias.endsWith(QLatin1String(".app"))) maintenanceToolAlias += QLatin1String(".app"); @@ -809,6 +937,13 @@ QString PackageManagerCorePrivate::offlineBinaryName() const return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename); } +QString PackageManagerCorePrivate::datFileName() +{ + if (m_datFileName.isEmpty()) + m_datFileName = targetDir() + QLatin1Char('/') + m_data.settings().maintenanceToolName() + QLatin1String(".dat"); + return m_datFileName; +} + static QNetworkProxy readProxy(QXmlStreamReader &reader) { QNetworkProxy proxy(QNetworkProxy::HttpProxy); @@ -862,12 +997,12 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() QVariantHash variables; // Do not change to QVariantMap! Breaks existing .ini files, // cause the variant types do not match while restoring the variables from the file. - QSettingsWrapper cfg(iniPath, QSettingsWrapper::IniFormat); + QSettingsWrapper cfg(iniPath, QSettings::IniFormat); foreach (const QString &key, m_data.keys()) { 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); } @@ -891,8 +1026,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(); @@ -923,7 +1058,10 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() writer.writeEndElement(); } writer.writeEndElement(); + writer.writeTextElement(QLatin1String("LocalCachePath"), m_data.settings().localCachePath()); writer.writeEndElement(); + + file.write(outputStr.toUtf8()); } setDefaultFilePermissions(&file, DefaultFilePermissions::NonExecutable); } @@ -931,7 +1069,7 @@ void PackageManagerCorePrivate::writeMaintenanceConfigFiles() void PackageManagerCorePrivate::readMaintenanceConfigFiles(const QString &targetDir) { QSettingsWrapper cfg(targetDir + QLatin1Char('/') + m_data.settings().maintenanceToolIniFile(), - QSettingsWrapper::IniFormat); + QSettings::IniFormat); const QVariantHash v = cfg.value(QLatin1String("Variables")).toHash(); // Do not change to // QVariantMap! Breaks reading from existing .ini files, cause the variant types do not match. for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); ++it) { @@ -963,7 +1101,7 @@ void PackageManagerCorePrivate::readMaintenanceConfigFiles(const QString &target case QXmlStreamReader::StartElement: { if (reader.name() == QLatin1String("Network")) { while (reader.readNextStartElement()) { - const QStringRef name = reader.name(); + const QStringView name = reader.name(); if (name == QLatin1String("Ftp")) { m_data.settings().setFtpProxy(readProxy(reader)); } else if (name == QLatin1String("Http")) { @@ -972,6 +1110,8 @@ void PackageManagerCorePrivate::readMaintenanceConfigFiles(const QString &target m_data.settings().addUserRepositories(readRepositories(reader, false)); } else if (name == QLatin1String("ProxyType")) { m_data.settings().setProxyType(Settings::ProxyType(reader.readElementText().toInt())); + } else if (name == QLatin1String("LocalCachePath")) { + m_data.settings().setLocalCachePath(reader.readElementText()); } else { reader.skipCurrentElement(); } @@ -1070,8 +1210,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++; } } } @@ -1150,12 +1293,7 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinary(QFile *const input, q // other code a lot (since installers don't have any appended data either) QFile dataOut(generateTemporaryFileName()); QInstaller::openForWrite(&dataOut); - QInstaller::appendInt64(&dataOut, 0); // operations start - QInstaller::appendInt64(&dataOut, 0); // operations end - QInstaller::appendInt64(&dataOut, 0); // resource count - QInstaller::appendInt64(&dataOut, 4 * sizeof(qint64)); // data block size - QInstaller::appendInt64(&dataOut, BinaryContent::MagicUninstallerMarker); - QInstaller::appendInt64(&dataOut, BinaryContent::MagicCookie); + QInstallerTools::createMTDatFile(dataOut); { QFile dummy(resourcePath.filePath(QLatin1String("installer.dat"))); @@ -1260,19 +1398,9 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinaryData(QFileDevice *outp QInstaller::appendInt64(output, BinaryContent::MagicUninstallerMarker); } -void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOperations) +void PackageManagerCorePrivate::writeMaintenanceToolAppBundle(OperationList &performedOperations) { - if (m_disableWriteMaintenanceTool) { - qCDebug(QInstaller::lcInstallerInstallLog()) << "Maintenance tool writing disabled."; - return; - } - - bool gainedAdminRights = false; - if (!directoryWritable(targetDir())) { - m_core->gainAdminRights(); - gainedAdminRights = true; - } - +#ifdef Q_OS_MACOS const QString targetAppDirPath = QFileInfo(maintenanceToolName()).path(); if (!QDir().exists(targetAppDirPath)) { // create the directory containing the maintenance tool (like a bundle structure on macOS...) @@ -1282,8 +1410,6 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper performOperationThreaded(op); performedOperations.append(takeOwnedOperation(op)); } - -#ifdef Q_OS_MACOS // if it is a bundle, we need some stuff in it... const QString sourceAppDirPath = QCoreApplication::applicationDirPath(); if (isInstaller() && QInstaller::isInBundle(sourceAppDirPath)) { @@ -1313,7 +1439,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper 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")); @@ -1353,7 +1479,23 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper << (targetAppDirPath + QLatin1String("/../plugins"))); performOperationThreaded(op); } +#else + Q_UNUSED(performedOperations); #endif +} + +void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOperations) +{ + if (m_disableWriteMaintenanceTool) { + qCDebug(QInstaller::lcInstallerInstallLog()) << "Maintenance tool writing disabled."; + return; + } + + bool gainedAdminRights = false; + if (!directoryWritable(targetDir())) { + m_core->gainAdminRights(); + gainedAdminRights = true; + } try { // 1 - check if we have a installer base replacement @@ -1389,45 +1531,71 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper // 5.1 - this will only happen -if- we wrote out a new binary bool newBinaryWritten = false; - bool replacementExists = false; + QString mtName = maintenanceToolName(); const QString installerBaseBinary = replaceVariables(m_installerBaseBinaryUnreplaced); - if (!installerBaseBinary.isEmpty() && QFileInfo(installerBaseBinary).exists()) { + if (!installerBaseBinary.isEmpty() && QFileInfo::exists(installerBaseBinary)) { qCDebug(QInstaller::lcInstallerInstallLog) << "Got a replacement installer base binary:" << installerBaseBinary; - - QFile replacementBinary(installerBaseBinary); - try { - QInstaller::openForRead(&replacementBinary); - writeMaintenanceToolBinary(&replacementBinary, replacementBinary.size(), true); - qCDebug(QInstaller::lcInstallerInstallLog) << "Wrote the binary with the new replacement."; - - newBinaryWritten = true; - replacementExists = true; - } catch (const Error &error) { - qCWarning(QInstaller::lcInstallerInstallLog) << error.message(); - } - - if (!replacementBinary.remove()) { - // Is there anything more sensible we can do with this error? I think not. It's not serious - // enough for throwing / aborting the process. - qCDebug(QInstaller::lcInstallerInstallLog) << "Cannot remove installer base binary" - << installerBaseBinary << "after updating the maintenance tool:" - << replacementBinary.errorString(); + if (QInstaller::isInBundle(installerBaseBinary)) { + // 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(); + } } else { - qCDebug(QInstaller::lcInstallerInstallLog) << "Removed installer base binary" - << installerBaseBinary << "after updating the maintenance tool."; + writeMaintenanceToolAppBundle(performedOperations); + QFile replacementBinary(installerBaseBinary); + try { + QInstaller::openForRead(&replacementBinary); + writeMaintenanceToolBinary(&replacementBinary, replacementBinary.size(), true); + qCDebug(QInstaller::lcInstallerInstallLog) << "Wrote the binary with the new replacement."; + + newBinaryWritten = true; + } catch (const Error &error) { + qCWarning(QInstaller::lcInstallerInstallLog) << error.message(); + } + + if (!replacementBinary.remove()) { + // Is there anything more sensible we can do with this error? I think not. It's not serious + // enough for throwing / aborting the process. + qCDebug(QInstaller::lcInstallerInstallLog) << "Cannot remove installer base binary" + << installerBaseBinary << "after updating the maintenance tool:" + << replacementBinary.errorString(); + } else { + qCDebug(QInstaller::lcInstallerInstallLog) << "Removed installer base binary" + << installerBaseBinary << "after updating the maintenance tool."; + } } m_installerBaseBinaryUnreplaced.clear(); - } else if (!installerBaseBinary.isEmpty() && !QFileInfo(installerBaseBinary).exists()) { + } else if (!installerBaseBinary.isEmpty() && !QFileInfo::exists(installerBaseBinary)) { qCWarning(QInstaller::lcInstallerInstallLog) << "The current maintenance tool could not be updated." << installerBaseBinary << "does not exist. Please fix the \"setInstallerBaseBinary" "(<temp_installer_base_binary_path>)\" call in your script."; + writeMaintenanceToolAppBundle(performedOperations); + } else { + writeMaintenanceToolAppBundle(performedOperations); } QFile input; BinaryLayout layout; - const QString dataFile = targetDir() + QLatin1Char('/') + m_data.settings().maintenanceToolName() - + QLatin1String(".dat"); + const QString dataFile = datFileName(); try { if (isInstaller()) { if (QFile::exists(dataFile)) { @@ -1512,23 +1680,32 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper if (newBinaryWritten) { // Remove old alias as the name may have changed. deleteMaintenanceToolAlias(); - // The new alias file is created after the maintenance too binary is renamed, - // but we need to set the value before the variables get written to disk. - m_core->setValue(QLatin1String("CreatedMaintenanceToolAlias"), maintenanceToolAliasPath()); + const QString aliasPath = maintenanceToolAliasPath(); + if (!aliasPath.isEmpty()) { + // The new alias file is created after the maintenance too binary is renamed, + // but we need to set the value before the variables get written to disk. + m_core->setValue(QLatin1String("CreatedMaintenanceToolAlias"), aliasPath); + } } #endif writeMaintenanceConfigFiles(); QFile::remove(dataFile); - QFile::rename(dataFile + QLatin1String(".new"), dataFile); + QFileInfo fi(mtName); + //Rename the dat file according to maintenancetool name + QFile::rename(dataFile + QLatin1String(".new"), targetDir() + QLatin1Char('/') + fi.baseName() + QLatin1String(".dat")); const bool restart = !statusCanceledOrFailed() && m_needsHardRestart; qCDebug(QInstaller::lcInstallerInstallLog) << "Maintenance tool hard restart:" << (restart ? "true." : "false."); if (newBinaryWritten) { - deferredRename(maintenanceToolName() + QLatin1String(".new"), maintenanceToolName(), restart); - writeMaintenanceToolAlias(); + if (isInstaller()) + QFile::rename(mtName + QLatin1String(".new"), mtName); + else + deferredRename(mtName + QLatin1String(".new"), mtName, restart); + QFileInfo mtFileName(mtName); + writeMaintenanceToolAlias(mtFileName.fileName()); } else if (restart) { SelfRestarter::setRestartOnQuit(true); } @@ -1600,21 +1777,25 @@ void PackageManagerCorePrivate::writeOfflineBaseBinary() } } -void PackageManagerCorePrivate::writeMaintenanceToolAlias() +void PackageManagerCorePrivate::writeMaintenanceToolAlias(const QString &maintenanceToolName) { #ifdef Q_OS_MACOS + const QString aliasPath = maintenanceToolAliasPath(); + if (aliasPath.isEmpty()) + return; + QString maintenanceToolBundle = QString::fromLatin1("%1/%2") - .arg(targetDir(), m_data.settings().maintenanceToolName()); + .arg(targetDir(), maintenanceToolName); if (!maintenanceToolBundle.endsWith(QLatin1String(".app"))) maintenanceToolBundle += QLatin1String(".app"); - const QString aliasPath = maintenanceToolAliasPath(); const QDir targetDir(QFileInfo(aliasPath).absolutePath()); - if (!targetDir.exists()) targetDir.mkpath(targetDir.absolutePath()); mkalias(maintenanceToolBundle, aliasPath); +#else + Q_UNUSED(maintenanceToolName) #endif } @@ -1702,7 +1883,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; @@ -1736,7 +1917,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")); @@ -1795,7 +1976,8 @@ bool PackageManagerCorePrivate::runInstaller() if (progress < 100) ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress); - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstallation finished!")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Installation finished!")); if (adminRightsGained) m_core->dropAdminRights(); @@ -1813,7 +1995,9 @@ bool PackageManagerCorePrivate::runInstaller() m_core->rollBackInstallation(); - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstallation aborted!")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Installation aborted!")); + if (adminRightsGained) m_core->dropAdminRights(); emit installationFinished(); @@ -1891,7 +2075,7 @@ bool PackageManagerCorePrivate::runPackageUpdater() // There is a replacement, but the replacement is not scheduled for update, keep it as well. if (m_componentsToReplaceUpdaterMode.contains(name) - && !m_componentsToReplaceUpdaterMode.value(name).first->updateRequested()) { + && !m_installerCalculator->resolvedComponents().contains(m_componentsToReplaceUpdaterMode.value(name).first)) { nonRevertedOperations.append(operation); continue; } @@ -1931,7 +2115,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(); } @@ -1952,7 +2136,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 @@ -1960,7 +2144,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")); @@ -1972,7 +2156,8 @@ bool PackageManagerCorePrivate::runPackageUpdater() // usually this should be only the reserved one from the beginning if (progress < 100) ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress); - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUpdate finished!")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Update finished!")); if (adminRightsGained) m_core->dropAdminRights(); @@ -1992,7 +2177,9 @@ bool PackageManagerCorePrivate::runPackageUpdater() m_core->rollBackInstallation(); - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUpdate aborted!")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Update aborted!")); + if (adminRightsGained) m_core->dropAdminRights(); emit installationFinished(); @@ -2026,7 +2213,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(); } @@ -2034,7 +2221,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 @@ -2043,7 +2230,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."; @@ -2119,7 +2306,7 @@ bool PackageManagerCorePrivate::runOfflineGenerator() m_core->downloadNeededArchives(double(1)); const QString installerBaseReplacement = replaceVariables(m_offlineBaseBinaryUnreplaced); - if (!installerBaseReplacement.isEmpty() && QFileInfo(installerBaseReplacement).exists()) { + if (!installerBaseReplacement.isEmpty() && QFileInfo::exists(installerBaseReplacement)) { qCDebug(QInstaller::lcInstallerInstallLog) << "Got a replacement installer base binary:" << offlineBinaryTempName; @@ -2205,12 +2392,13 @@ bool PackageManagerCorePrivate::runOfflineGenerator() } void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &components, - double progressOperationSize, bool adminRightsGained) + double progressOperationSize) { OperationList unpackOperations; bool becameAdmin = false; - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nPreparing to unpack components...")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Preparing to unpack components...")); // 1. Collect operations quint64 totalOperationsSizeHint = 0; @@ -2228,7 +2416,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(); } } @@ -2276,7 +2464,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(); } @@ -2291,11 +2479,12 @@ void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &compo return lhs->sizeHint() > rhs->sizeHint(); }); - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUnpacking components...")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Unpacking components...")); runner.setType(Operation::Perform); const QHash<Operation *, bool> results = runner.run(); - const OperationList performedOperations = backupResults.keys(); + const OperationList performedOperations = results.keys(); QString error; for (auto &operation : performedOperations) { @@ -2344,8 +2533,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()) @@ -2355,8 +2543,8 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr // show only components which do something, MinimumProgress is only for progress calculation safeness bool showDetailsLog = false; if (opCount > 1 || (opCount == 1 && operations.at(0)->name() != QLatin1String("MinimumProgress"))) { - ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstalling component %1") - .arg(component->displayName())); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QLatin1Char('\n') + + tr("Installing component %1").arg(component->displayName())); showDetailsLog = true; } @@ -2366,7 +2554,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; } @@ -2455,23 +2643,54 @@ 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)) { + 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 { + qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage + << "No components available with the current selection."; + } + } return true; + }; + + if (!fetchComponents() && !fallbackReposFetched) { + setStatus(PackageManagerCore::Running); + enableAllCategories(); + fetchComponents(); } - return false; + + return m_core->status(); } void PackageManagerCorePrivate::setComponentSelection(const QString &id, Qt::CheckState state) @@ -2492,6 +2711,14 @@ void PackageManagerCorePrivate::setComponentSelection(const QString &id, Qt::Che void PackageManagerCorePrivate::deleteMaintenanceTool() { + QDir resourcePath(QFileInfo(maintenanceToolName()).dir()); + resourcePath.remove(QLatin1String("installer.dat")); + QDir installDir(targetDir()); + installDir.remove(m_data.settings().maintenanceToolName() + QLatin1String(".dat")); + installDir.remove(QLatin1String("network.xml")); + installDir.remove(m_data.settings().maintenanceToolIniFile()); + QInstaller::VerboseWriter::instance()->setFileName(QString()); + installDir.remove(m_core->value(QLatin1String("LogFileName"), QLatin1String("InstallationLog.txt"))); #ifdef Q_OS_WIN // Since Windows does not support that the maintenance tool deletes itself we have to go with a rather dirty // hack. What we do is to create a batchfile that will try to remove the maintenance tool once per second. Then @@ -2504,6 +2731,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool() if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) throw Error(tr("Cannot prepare removal")); + const bool removeTargetDir = QVariant(m_core->value(scRemoveTargetDir)).toBool(); QTextStream batch(&f); batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n"; batch << "file = WScript.Arguments.Item(0)\n"; @@ -2515,10 +2743,12 @@ void PackageManagerCorePrivate::deleteMaintenanceTool() batch << " fso.DeleteFile(file)\n"; batch << " WScript.Sleep(1000)\n"; batch << "wend\n"; -// batch << "if folder.SubFolders.Count = 0 and folder.Files.Count = 0 then\n"; + if (!removeTargetDir) + batch << "if folder.SubFolders.Count = 0 and folder.Files.Count = 0 then\n"; batch << " Set folder = Nothing\n"; batch << " fso.DeleteFolder folderpath, true\n"; -// batch << "end if\n"; + if (!removeTargetDir) + batch << "end if\n"; batch << "fso.DeleteFile(WScript.ScriptFullName)\n"; f.close(); @@ -2526,11 +2756,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool() QStringList arguments; arguments << QLatin1String("//Nologo") << batchfile; // execute the batchfile arguments << QDir::toNativeSeparators(QFileInfo(installerBinaryPath()).absoluteFilePath()); - if (!m_performedOperationsOld.isEmpty()) { - const Operation *const op = m_performedOperationsOld.first(); - if (op->name() == QLatin1String("Mkdir")) // the target directory name - arguments << QDir::toNativeSeparators(QFileInfo(op->arguments().first()).absoluteFilePath()); - } + arguments << targetDir(); if (!QProcessWrapper::startDetached(QLatin1String("cscript"), arguments, QDir::rootPath())) throw Error(tr("Cannot start removal")); @@ -2570,7 +2796,11 @@ void PackageManagerCorePrivate::deleteMaintenanceToolAlias() void PackageManagerCorePrivate::registerMaintenanceTool() { #ifdef Q_OS_WIN - QSettingsWrapper settings(registerPath(), QSettingsWrapper::NativeFormat); + auto quoted = [](const QString &s) { + return QString::fromLatin1("\"%1\"").arg(s); + }; + + QSettingsWrapper settings(registerPath(), QSettings::NativeFormat); settings.setValue(scDisplayName, m_data.value(QLatin1String("ProductName"))); settings.setValue(QLatin1String("DisplayVersion"), m_data.value(QLatin1String("ProductVersion"))); const QString maintenanceTool = QDir::toNativeSeparators(maintenanceToolName()); @@ -2580,9 +2810,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"), maintenanceTool); - settings.setValue(QLatin1String("ModifyPath"), QString(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 @@ -2614,13 +2847,13 @@ void PackageManagerCorePrivate::registerMaintenanceTool() void PackageManagerCorePrivate::unregisterMaintenanceTool() { #ifdef Q_OS_WIN - QSettingsWrapper settings(registerPath(), QSettingsWrapper::NativeFormat); + QSettingsWrapper settings(registerPath(), QSettings::NativeFormat); settings.remove(QString()); #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(); @@ -2631,7 +2864,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); @@ -2739,6 +2972,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; @@ -2769,12 +3021,12 @@ bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories(DownloadTyp return m_repoFetched; } -bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChecksum, bool compressedRepository) +bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool compressedRepository) { if (!compressedRepository && m_updateSourcesAdded) return m_updateSourcesAdded; - const QList<Metadata> metadata = m_metadataJob.metadata(); + const QList<Metadata *> metadata = m_metadataJob.metadata(); if (metadata.isEmpty()) { m_updateSourcesAdded = true; return m_updateSourcesAdded; @@ -2787,53 +3039,25 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe m_updates = false; m_updateSourcesAdded = false; if (isInstaller()) - m_packageSources.insert(PackageSource(QUrl(QLatin1String("resource://metadata/")), 0)); + m_packageSources.insert(PackageSource(QUrl(QLatin1String("resource://metadata/")), 1)); } - foreach (const Metadata &data, metadata) { - if (compressedRepository && !data.repository.isCompressed()) { + foreach (const Metadata *data, metadata) { + if (compressedRepository && !data->repository().isCompressed()) { continue; } if (statusCanceledOrFailed()) return false; - if (data.directory.isEmpty()) + if (data->path().isEmpty()) continue; - if (parseChecksum) { - const QString updatesXmlPath = data.directory + QLatin1String("/Updates.xml"); - QFile updatesFile(updatesXmlPath); - try { - QInstaller::openForRead(&updatesFile); - } catch(const Error &e) { - qCWarning(QInstaller::lcInstallerInstallLog) << "Error opening Updates.xml:" - << e.message(); - setStatus(PackageManagerCore::Failure, tr("Cannot add temporary update source information.")); - return false; - } - - int line = 0; - int column = 0; - QString error; - QDomDocument doc; - if (!doc.setContent(&updatesFile, &error, &line, &column)) { - qCWarning(QInstaller::lcInstallerInstallLog).nospace() << "Parse error in file " - << updatesFile.fileName() << ": " << error << " at line " << line - << " col " << column; - setStatus(PackageManagerCore::Failure, tr("Cannot add temporary update source information.")); - return false; - } - - const QDomNode checksum = doc.documentElement().firstChildElement(QLatin1String("Checksum")); - if (!checksum.isNull()) - m_core->setTestChecksum(checksum.toElement().text().toLower() == scTrue); - } - if (data.repository.isCompressed()) - m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 2)); + if (data->repository().isCompressed()) + m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 2, data->repository().postLoadComponentScript())); else - m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 1)); + m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 0, data->repository().postLoadComponentScript())); - ProductKeyCheck::instance()->addPackagesFromXml(data.directory + QLatin1String("/Updates.xml")); + ProductKeyCheck::instance()->addPackagesFromXml(data->path() + QLatin1String("/Updates.xml")); } if ((compressedRepository && m_compressedPackageSources.count() == 0 ) || (!compressedRepository && m_packageSources.count() == 0)) { @@ -2859,7 +3083,6 @@ void PackageManagerCorePrivate::restoreCheckState() } m_coreCheckedHash.clear(); - m_componentsToInstallCalculated = false; } void PackageManagerCorePrivate::storeCheckState() @@ -2872,19 +3095,76 @@ void PackageManagerCorePrivate::storeCheckState() m_coreCheckedHash.insert(component, component->checkState()); } -void PackageManagerCorePrivate::updateComponentCheckedState() +void PackageManagerCorePrivate::updateComponentInstallActions() { for (Component *component : m_core->components(PackageManagerCore::ComponentType::All)) { component->setInstallAction(component->isInstalled() ? ComponentModelHelper::KeepInstalled : ComponentModelHelper::KeepUninstalled); } - for (Component *component : uninstallerCalculator()->componentsToUninstall()) + for (Component *component : uninstallerCalculator()->resolvedComponents()) component->setInstallAction(ComponentModelHelper::Uninstall); - for (Component *component : installerCalculator()->orderedComponentsToInstall()) + for (Component *component : installerCalculator()->resolvedComponents()) 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( + components, + [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::connectOperationCallMethodRequest(Operation *const operation) { QObject *const operationObject = dynamic_cast<QObject *> (operation); @@ -2935,17 +3215,26 @@ void PackageManagerCorePrivate::handleMethodInvocationRequest(const QString &inv QMetaObject::invokeMethod(obj, qPrintable(invokableMethodName)); } +/* + Adds the \a path for deletetion. Unlike files for delayed deletion, which are deleted + on the start of next installer run, these paths are deleted on exit. +*/ +void PackageManagerCorePrivate::addPathForDeletion(const QString &path) +{ + m_tmpPathDeleter.add(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.") @@ -2971,36 +3260,24 @@ 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() { - QString htmlOutput; - bool componentsOk = m_core->calculateComponents(&htmlOutput); + bool componentsOk = m_core->recalculateAllComponents(); + if (statusCanceledOrFailed()) { qCDebug(QInstaller::lcInstallerInstallLog) << "Installation canceled."; } else if (componentsOk && acceptLicenseAgreements()) { - qCDebug(QInstaller::lcInstallerInstallLog).noquote() << htmlToString(htmlOutput); + try { + loadComponentScripts(installerCalculator()->resolvedComponents(), true); + } catch (const Error &error) { + qCWarning(QInstaller::lcInstallerInstallLog) << error.message(); + return false; + } + 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."; @@ -3013,31 +3290,6 @@ bool PackageManagerCorePrivate::calculateComponentsAndRun() return false; } -void PackageManagerCorePrivate::calculateUninstallComponents() -{ - clearUninstallerCalculator(); - const QList<Component *> componentsToInstallList = installerCalculator()->orderedComponentsToInstall(); - QSet<Component*> componentsToInstall(componentsToInstallList.begin(), componentsToInstallList.end()); - - QList<Component *> selectedComponentsToUninstall; - foreach (Component* component, m_core->components(PackageManagerCore::ComponentType::Replacements)) { - // Uninstall the component if replacement is selected for install or update - QPair<Component*, Component*> comp = componentsToReplace().value(component->name()); - if (comp.first) { - if (comp.first->isSelectedForInstallation() || comp.first->updateRequested()) { - uninstallerCalculator()->insertUninstallReason(component, - UninstallerCalculator::Replaced, comp.first->name()); - selectedComponentsToUninstall.append(comp.second); - } - } - } - foreach (Component *component, m_core->components(PackageManagerCore::ComponentType::AllNoReplacements)) { - if (component->uninstallationRequested() && !componentsToInstallList.contains(component)) - selectedComponentsToUninstall.append(component); - } - uninstallerCalculator()->appendComponentsToUninstall(selectedComponentsToUninstall); -} - bool PackageManagerCorePrivate::acceptLicenseAgreements() const { // Always skip for uninstaller @@ -3052,6 +3304,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(); @@ -3100,6 +3359,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?"; @@ -3155,20 +3431,38 @@ void PackageManagerCorePrivate::commitPendingUnstableComponents() m_pendingUnstableComponents.clear(); } -void PackageManagerCorePrivate::createDependencyHashes(const Component* component) +void PackageManagerCorePrivate::createAutoDependencyHash(const QString &component, const QString &oldDependencies, const QString &newDependencies) { - for (const QString &autodepend : component->autoDependencies()) { + // User might have changed autodependencies with setValue. Remove the old values. + const QStringList oldDependencyList = oldDependencies.split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); + for (const QString &removedDependency : oldDependencyList) { + QStringList value = m_autoDependencyComponentHash.value(removedDependency); + value.removeAll(component); + if (value.isEmpty()) + m_autoDependencyComponentHash.remove(removedDependency); + else + m_autoDependencyComponentHash.insert(removedDependency, value); + } + + const QStringList newDependencyList = newDependencies.split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); + for (const QString &autodepend : newDependencyList) { QStringList value = m_autoDependencyComponentHash.value(autodepend); - if (!value.contains(component->name())) - value.append(component->name()); - m_autoDependencyComponentHash.insert(autodepend, value); + if (!value.contains(component)) { + value.append(component); + m_autoDependencyComponentHash.insert(autodepend, value); + } } +} - for (const QString &depend : component->dependencies()) { - QStringList value = m_dependencyComponentHash.value(depend); - if (!value.contains(component->name())) - value.append(component->name()); - m_dependencyComponentHash.insert(depend, value); +void PackageManagerCorePrivate::createLocalDependencyHash(const QString &component, const QString &dependencies) +{ + const QStringList localDependencies = dependencies.split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); + for (const QString &depend : localDependencies) { + QStringList value = m_localDependencyComponentHash.value(depend); + if (!value.contains(component)) { + value.append(component); + m_localDependencyComponentHash.insert(depend, value); + } } } |