diff options
Diffstat (limited to 'src/libs/ifwtools/repositorygen.cpp')
-rw-r--r-- | src/libs/ifwtools/repositorygen.cpp | 197 |
1 files changed, 140 insertions, 57 deletions
diff --git a/src/libs/ifwtools/repositorygen.cpp b/src/libs/ifwtools/repositorygen.cpp index a045c5248..9232a02d4 100644 --- a/src/libs/ifwtools/repositorygen.cpp +++ b/src/libs/ifwtools/repositorygen.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. @@ -34,6 +34,7 @@ #include "errors.h" #include "globals.h" #include "archivefactory.h" +#include "metadata.h" #include "settings.h" #include "qinstallerglobal.h" #include "utils.h" @@ -42,7 +43,7 @@ #include "updater.h" #include <QtCore/QDirIterator> -#include <QtCore/QRegExp> +#include <QtCore/QRegularExpression> #include <QtXml/QDomDocument> #include <QTemporaryDir> @@ -128,6 +129,75 @@ static QStringList copyFilesFromNode(const QString &parentNode, const QString &c return copiedFiles; } +/* + Returns \c true if the \a file is an archive or an SHA1 checksum + file for an archive, /c false otherwise. +*/ +static bool isArchiveOrChecksum(const QString &file) +{ + if (file.endsWith(QLatin1String(".sha1"))) + return true; + + for (auto &supportedSuffix : ArchiveFactory::supportedTypes()) { + if (file.endsWith(supportedSuffix)) + return true; + } + return false; +} + +/* + Fills the package \a info with the name of the metadata archive when applicable. Returns + \c true if the component has metadata compressed in an archive or uncompressed to cache, or + if the metadata archive is redundant. Returns \c false if the component should have metadata + but none was found. +*/ +static bool findMetaFile(const QString &repositoryDir, const QDomElement &packageUpdate, PackageInfo &info) +{ + // Note: the order here is important, when updating from an existing + // repository we shouldn't drop the empty metadata archives. + + // 1. First, try with normal repository structure + QString metaFile = QString::fromLatin1("%1/%3%2").arg(info.directory, + QString::fromLatin1("meta.7z"), info.version); + + if (QFileInfo::exists(metaFile)) { + info.metaFile = metaFile; + return true; + } + + // 2. If that does not work, check for fetched temporary repository structure + metaFile = QString::fromLatin1("%1/%2-%3-%4").arg(repositoryDir, + info.name, info.version, QString::fromLatin1("meta.7z")); + + if (QFileInfo::exists(metaFile)) { + info.metaFile = metaFile; + return true; + } + + // 3. Try with the cached metadata directory structure + const QDir packageDir(info.directory); + const QStringList cachedMetaFiles = packageDir.entryList(QDir::Files); + for (auto &file : cachedMetaFiles) { + if (!isArchiveOrChecksum(file)) + return true; // Return for first non-archive file + } + + // 4. The meta archive may be redundant, skip in that case (cached item from a + // repository that has empty meta archive) + bool metaElementFound = false; + const QDomNodeList c1 = packageUpdate.childNodes(); + for (int i = 0; i < c1.count(); ++i) { + const QDomElement e1 = c1.at(i).toElement(); + for (const QString &meta : scMetaElements) { + if (e1.tagName() == meta) { + metaElementFound = true; + break; + } + } + } + return !metaElementFound; +} + void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &metaDataDir, const PackageInfoVector &packages, const QString &appName, const QString &appVersion, const QStringList &uniteMetadatas) @@ -297,39 +367,8 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met root.appendChild(update); - // copy script file - const QString script = package.firstChildElement(QLatin1String("Script")).text(); - if (!script.isEmpty()) { - QFile scriptFile(QString::fromLatin1("%1/meta/%2").arg(info.directory, script)); - if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - throw QInstaller::Error(QString::fromLatin1("Cannot open component script at \"%1\".") - .arg(QDir::toNativeSeparators(scriptFile.fileName()))); - } - - const QString scriptContent = QLatin1String("(function() {") - + QString::fromUtf8(scriptFile.readAll()) - + QLatin1String(";" - " if (typeof Component == \"undefined\")" - " throw \"Missing Component constructor. Please check your script.\";" - "})();"); - - // if the user isn't aware of the downloadable archives value we will add it automatically later - foundDownloadableArchives |= scriptContent.contains(QLatin1String("addDownloadableArchive")) - || scriptContent.contains(QLatin1String("removeDownloadableArchive")); - - static QInstaller::ScriptEngine testScriptEngine; - const QJSValue value = testScriptEngine.evaluate(scriptContent, scriptFile.fileName()); - if (value.isError()) { - throw QInstaller::Error(QString::fromLatin1("Exception while loading component " - "script at \"%1\": %2").arg(QDir::toNativeSeparators(scriptFile.fileName()), - value.toString().isEmpty() ? QString::fromLatin1("Unknown error.") : - value.toString() + QStringLiteral(" on line number: ") + - value.property(QStringLiteral("lineNumber")).toString())); - } - - const QString toLocation(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, script)); - copyWithException(scriptFile.fileName(), toLocation, QInstaller::scScript); - } + // copy script files + copyScriptFiles(childNodes, info, foundDownloadableArchives, targetDir); // write DownloadableArchives tag if that is missed by the user if (!foundDownloadableArchives && !info.copiedFiles.isEmpty()) { @@ -338,7 +377,7 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met if (!filePath.endsWith(QLatin1String(".sha1"), Qt::CaseInsensitive)) { const QString fileName = QFileInfo(filePath).fileName(); // remove unnecessary version string from filename and add it to the list - realContentFiles.append(fileName.mid(info.version.count())); + realContentFiles.append(fileName.mid(info.version.size())); } } @@ -398,6 +437,19 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met throw Error(QString::fromLatin1("Could not extract archive \"%1\": %2").arg( QDir::toNativeSeparators(info.metaFile), metaFile->errorString())); } + } else { + // The metadata may have been already extracted, i.e. when reading from a + // local repository cache. + const QDir packageDir(info.directory); + const QStringList metaFiles = packageDir.entryList(QDir::Files); + for (auto &file : metaFiles) { + if (isArchiveOrChecksum(file)) + continue; // Skip data archives + + const QString source(QString::fromLatin1("%1/%2").arg(info.directory, file)); + const QString target(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, file)); + copyWithException(source, target, QLatin1String("cached metadata")); + } } // Restore "PackageUpdate" node; @@ -517,15 +569,15 @@ PackageInfoVector QInstallerTools::createListOfPackages(const QStringList &packa info.version = packageElement.firstChildElement(QLatin1String("Version")).text(); // Version cannot start with comparison characters, be an empty string // or have whitespaces at the beginning or at the end - if (!QRegExp(QLatin1String("(?![<=>\\s]+)(.+)")).exactMatch(info.version) || - (info.version != info.version.trimmed())) { + static const QRegularExpression regex(QLatin1String("^(?![<=>\\s]+)(.+)$")); + if (!regex.match(info.version).hasMatch() || (info.version != info.version.trimmed())) { if (ignoreInvalidPackages) continue; throw QInstaller::Error(QString::fromLatin1("Component version for \"%1\" is invalid! <Version>%2</Version>") .arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()), info.version)); } info.dependencies = packageElement.firstChildElement(QLatin1String("Dependencies")).text() - .split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); info.directory = it->filePath(); if (packagesUpdatedWithSha.contains(info.name)) { info.createContentSha1Node = true; @@ -639,22 +691,9 @@ PackageInfoVector QInstallerTools::createListOfRepositoryPackages(const QStringL info.directory = QString::fromLatin1("%1/%2").arg(it->filePath(), info.name); if (!hasUnifiedMetaFile) { const QDomElement sha1 = el.firstChildElement(QInstaller::scSHA1); - if (!sha1.isNull()) { - // 1. First, try with normal repository structure - QString metaFile = QString::fromLatin1("%1/%3%2").arg(info.directory, - QString::fromLatin1("meta.7z"), info.version); - - if (!QFileInfo(metaFile).exists()) { - // 2. If that does not work, check for fetched temporary repository structure - metaFile = QString::fromLatin1("%1/%2-%3-%4").arg(it->filePath(), - info.name, info.version, QString::fromLatin1("meta.7z")); - - if (!QFileInfo(metaFile).exists()) { - throw QInstaller::Error(QString::fromLatin1("Could not find meta archive for component " - "%1 %2 in repository %3.").arg(info.name, info.version, it->filePath())); - } - } - info.metaFile = metaFile; + if (!sha1.isNull() && !findMetaFile(it->filePath(), el, info)) { + throw QInstaller::Error(QString::fromLatin1("Could not find metadata archive for component " + "%1 %2 in repository %3.").arg(info.name, info.version, it->filePath())); } } @@ -663,10 +702,10 @@ PackageInfoVector QInstallerTools::createListOfRepositoryPackages(const QStringL const QDomElement c2Element = c2.at(j).toElement(); if (c2Element.tagName() == QInstaller::scDependencies) info.dependencies = c2Element.text() - .split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); else if (c2Element.tagName() == QInstaller::scDownloadableArchives) { QStringList names = c2Element.text() - .split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); foreach (const QString &name, names) { info.copiedFiles.append(QString::fromLatin1("%1/%3%2").arg(info.directory, name, info.version)); @@ -907,6 +946,50 @@ void QInstallerTools::splitMetadata(const QStringList &entryList, const QString } } +void QInstallerTools::copyScriptFiles(const QDomNodeList &childNodes, const PackageInfo &info, bool &foundDownloadableArchives, const QString &targetDir) +{ + for (int i = 0; i < childNodes.count(); ++i) { + const QDomNode node = childNodes.at(i); + const QString key = node.nodeName(); + + if (key != QLatin1String("Script")) + continue; + const QString script = node.toElement().text(); + if (script.isEmpty()) + continue; + + QFile scriptFile(QString::fromLatin1("%1/meta/%2").arg(info.directory, script)); + if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + throw QInstaller::Error(QString::fromLatin1("Cannot open component script at \"%1\".") + .arg(QDir::toNativeSeparators(scriptFile.fileName()))); + } + + const QString scriptContent = QLatin1String("(function() {") + + QString::fromUtf8(scriptFile.readAll()) + + QLatin1String(";" + " if (typeof Component == \"undefined\")" + " throw \"Missing Component constructor. Please check your script.\";" + "})();"); + + // if the user isn't aware of the downloadable archives value we will add it automatically later + foundDownloadableArchives |= scriptContent.contains(QLatin1String("addDownloadableArchive")) + || scriptContent.contains(QLatin1String("removeDownloadableArchive")); + + static QInstaller::ScriptEngine testScriptEngine; + const QJSValue value = testScriptEngine.evaluate(scriptContent, scriptFile.fileName()); + if (value.isError()) { + throw QInstaller::Error(QString::fromLatin1("Exception while loading component " + "script at \"%1\": %2").arg(QDir::toNativeSeparators(scriptFile.fileName()), + value.toString().isEmpty() ? QString::fromLatin1("Unknown error.") : + value.toString() + QStringLiteral(" on line number: ") + + value.property(QStringLiteral("lineNumber")).toString())); + } + + const QString toLocation(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, script)); + copyWithException(scriptFile.fileName(), toLocation, QInstaller::scScript); + } +} + void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QString &repoDir, PackageInfoVector *const infos, const QString &archiveSuffix, Compression compression) { |