summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/includes/IFWDoc2
-rw-r--r--doc/installerfw.qdoc12
-rw-r--r--doc/scripting-api/packagemanagercore.qdoc6
-rw-r--r--src/libs/ifwtools/repositorygen.cpp104
-rw-r--r--src/libs/installer/constants.h5
-rw-r--r--src/libs/installer/downloadarchivesjob.cpp5
-rw-r--r--src/libs/installer/downloadarchivesjob.h3
-rw-r--r--src/libs/installer/fileutils.cpp28
-rw-r--r--src/libs/installer/fileutils.h10
-rw-r--r--src/libs/installer/genericdatacache.cpp619
-rw-r--r--src/libs/installer/genericdatacache.h111
-rw-r--r--src/libs/installer/installer.pro4
-rw-r--r--src/libs/installer/metadata.cpp257
-rw-r--r--src/libs/installer/metadata.h81
-rw-r--r--src/libs/installer/metadatajob.cpp385
-rw-r--r--src/libs/installer/metadatajob.h41
-rw-r--r--src/libs/installer/metadatajob_p.h4
-rw-r--r--src/libs/installer/packagemanagercore.cpp17
-rw-r--r--src/libs/installer/packagemanagercore.h1
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp29
-rw-r--r--src/libs/installer/packagemanagercore_p.h4
-rw-r--r--src/libs/installer/qinstallerglobal.h3
-rw-r--r--src/libs/installer/repository.cpp3
-rw-r--r--src/libs/installer/settings.cpp41
-rw-r--r--src/libs/installer/settings.h11
-rw-r--r--src/sdk/settingsdialog.cpp19
-rw-r--r--src/sdk/settingsdialog.h4
-rw-r--r--src/sdk/settingsdialog.ui60
-rw-r--r--src/sdk/tabcontroller.cpp4
-rw-r--r--tests/auto/installer/appendfileoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/commandlineinstall/data/filequeryrepository/Updates.xml2
-rw-r--r--tests/auto/installer/commandlineinstall/tst_commandlineinstall.cpp10
-rw-r--r--tests/auto/installer/consumeoutputoperationtest/data/repository/Updates.xml1
-rw-r--r--tests/auto/installer/copydirectoryoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/copydirectoryoperation/data/xmloperationrepository/Updates.xml2
-rw-r--r--tests/auto/installer/createdesktopentryoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/createshortcutoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/deleteoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/environmentvariableoperation/data/repository/Updates.xml1
-rw-r--r--tests/auto/installer/installiconsoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/licenseagreement/data/repository/Updates.xml1
-rw-r--r--tests/auto/installer/messageboxhandler/data/invalidoperation/Updates.xml2
-rw-r--r--tests/auto/installer/messageboxhandler/data/messagebox/Updates.xml2
-rw-r--r--tests/auto/installer/moveoperation/data/repository/Updates.xml1
-rw-r--r--tests/auto/installer/registerfiletypeoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/replaceoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/repository/data/compressedRepository/compressedRepository.7zbin542 -> 590 bytes
-rw-r--r--tests/auto/installer/repository/data/repository/Updates.xml1
-rw-r--r--tests/auto/installer/settingsoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/installer/simplemovefileoperation/data/repository/Updates.xml2
-rw-r--r--tests/auto/tools/repotest/test_package_versions/repository_1/Updates.xml4
-rw-r--r--tests/auto/tools/repotest/tst_repotest.cpp2
52 files changed, 1724 insertions, 198 deletions
diff --git a/doc/includes/IFWDoc b/doc/includes/IFWDoc
index fd86176ea..6e5b4dbd4 100644
--- a/doc/includes/IFWDoc
+++ b/doc/includes/IFWDoc
@@ -34,6 +34,7 @@
#include "fileguard.h"
#include "fileio.h"
#include "fileutils.h"
+#include "genericdatacache.h"
#include "globalsettingsoperation.h"
#include "globals.h"
#include "graph.h"
@@ -56,6 +57,7 @@
#include "directoryguard.h"
#include "link.h"
#include "messageboxhandler.h"
+#include "metadata.h"
#include "metadatajob.h"
#include "minimumprogressoperation.h"
#include "observer.h"
diff --git a/doc/installerfw.qdoc b/doc/installerfw.qdoc
index f10d0ba67..29399f0ba 100644
--- a/doc/installerfw.qdoc
+++ b/doc/installerfw.qdoc
@@ -494,6 +494,18 @@
rights. Only available on Linux, where you usually do not want
to install in the administrator user's home directory.
\row
+ \li LocalCacheDir
+ \li Directory name for storing the metadata cache. This does not include
+ the leading directories, which are determined automatically based on a suitable
+ platform specific location for storing cache files. The user may override the path
+ from the installer settings. The default value is a UUID generated from the
+ name of the product being installed.
+ \row
+ \li PersistentLocalCache
+ \li Set to \c false if the fetched metadata should be removed from the local cache when
+ the installer exits. Otherwise the contents of the cache are kept to speed up
+ subsequent fetches. Defaults to \c true.
+ \row
\li RemoteRepositories
\li List of remote repositories. This element can contain several \c <Repository> child
elements that each contain the \c <Url> child element that specifies the URL to
diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc
index b14fd957f..cd2253890 100644
--- a/doc/scripting-api/packagemanagercore.qdoc
+++ b/doc/scripting-api/packagemanagercore.qdoc
@@ -325,6 +325,12 @@
*/
/*!
+ \qmlmethod void installer::clearLocalCache()
+
+ Clears the contents of the cache used to store downloaded metadata.
+*/
+
+/*!
\qmlmethod void installer::componentsToInstallNeedsRecalculation()
Ensures that component dependencies are re-calculated.
diff --git a/src/libs/ifwtools/repositorygen.cpp b/src/libs/ifwtools/repositorygen.cpp
index 874d88494..5bd9fd334 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) 2022 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"
@@ -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)
@@ -398,6 +468,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;
@@ -639,22 +722,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()));
}
}
diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h
index 0973e8c3e..38223eaf3 100644
--- a/src/libs/installer/constants.h
+++ b/src/libs/installer/constants.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -70,6 +70,7 @@ static const QLatin1String scUncompressedSizeSum("UncompressedSizeSum");
static const QLatin1String scRequiresAdminRights("RequiresAdminRights");
static const QLatin1String scOfflineBinaryName("OfflineBinaryName");
static const QLatin1String scSHA1("SHA1");
+static const QLatin1String scMetadataName("MetadataName");
static const QLatin1String scContentSha1("ContentSha1");
// constants used throughout the components class
@@ -84,6 +85,8 @@ static const QLatin1String scRunProgram("RunProgram");
static const QLatin1String scRunProgramArguments("RunProgramArguments");
static const QLatin1String scStartMenuDir("StartMenuDir");
static const QLatin1String scRemoveTargetDir("RemoveTargetDir");
+static const QLatin1String scLocalCacheDir("LocalCacheDir");
+static const QLatin1String scPersistentLocalCache("PersistentLocalCache");
static const QLatin1String scRunProgramDescription("RunProgramDescription");
static const QLatin1String scTargetConfigurationFile("TargetConfigurationFile");
static const QLatin1String scAllowNonAsciiCharacters("AllowNonAsciiCharacters");
diff --git a/src/libs/installer/downloadarchivesjob.cpp b/src/libs/installer/downloadarchivesjob.cpp
index 4d17ef4a2..a019d1567 100644
--- a/src/libs/installer/downloadarchivesjob.cpp
+++ b/src/libs/installer/downloadarchivesjob.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -146,6 +146,7 @@ void DownloadArchivesJob::finishedHashDownload()
QFile sha1HashFile(m_downloader->downloadedFileName());
if (sha1HashFile.open(QFile::ReadOnly)) {
+ emit hashDownloadReady(m_downloader->downloadedFileName());
m_currentHash = sha1HashFile.readAll();
fetchNextArchive();
} else {
@@ -310,6 +311,8 @@ void DownloadArchivesJob::registerFile()
const QPair<QString, QString> pair = m_archivesToDownload.takeFirst();
BinaryFormatEngineHandler::instance()->registerResource(pair.first,
m_downloader->downloadedFileName());
+
+ emit fileDownloadReady(m_downloader->downloadedFileName());
}
fetchNextArchiveHash();
}
diff --git a/src/libs/installer/downloadarchivesjob.h b/src/libs/installer/downloadarchivesjob.h
index c69291d69..c75e9307a 100644
--- a/src/libs/installer/downloadarchivesjob.h
+++ b/src/libs/installer/downloadarchivesjob.h
@@ -64,6 +64,9 @@ Q_SIGNALS:
void outputTextChanged(const QString &progress);
void downloadStatusChanged(const QString &status);
+ void hashDownloadReady(const QString &localPath);
+ void fileDownloadReady(const QString &localPath);
+
protected:
void doStart() override;
void doCancel() override;
diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp
index 5a89073fc..6bd79d4dd 100644
--- a/src/libs/installer/fileutils.cpp
+++ b/src/libs/installer/fileutils.cpp
@@ -66,54 +66,62 @@ using namespace QInstaller;
/*!
\inmodule QtInstallerFramework
- \class QInstaller::TempDirDeleter
+ \class QInstaller::TempPathDeleter
\internal
*/
// -- TempDirDeleter
-TempDirDeleter::TempDirDeleter(const QString &path)
+TempPathDeleter::TempPathDeleter(const QString &path)
{
m_paths.insert(path);
}
-TempDirDeleter::TempDirDeleter(const QStringList &paths)
+TempPathDeleter::TempPathDeleter(const QStringList &paths)
: m_paths(QSet<QString>(paths.begin(), paths.end()))
{
}
-TempDirDeleter::~TempDirDeleter()
+TempPathDeleter::~TempPathDeleter()
{
releaseAndDeleteAll();
}
-QStringList TempDirDeleter::paths() const
+QStringList TempPathDeleter::paths() const
{
return m_paths.toList();
}
-void TempDirDeleter::add(const QString &path)
+void TempPathDeleter::add(const QString &path)
{
m_paths.insert(path);
}
-void TempDirDeleter::add(const QStringList &paths)
+void TempPathDeleter::add(const QStringList &paths)
{
m_paths += QSet<QString>(paths.begin(), paths.end());
}
-void TempDirDeleter::releaseAndDeleteAll()
+void TempPathDeleter::releaseAndDeleteAll()
{
foreach (const QString &path, m_paths)
releaseAndDelete(path);
}
-void TempDirDeleter::releaseAndDelete(const QString &path)
+void TempPathDeleter::releaseAndDelete(const QString &path)
{
if (m_paths.contains(path)) {
try {
m_paths.remove(path);
- removeDirectory(path);
+ if (QFileInfo(path).isDir()) {
+ removeDirectory(path);
+ return;
+ }
+ QFile file(path);
+ if (file.exists() && !file.remove()) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot remove file \"%1\": %3").arg(file.fileName(), file.errorString()));
+ }
} catch (const Error &e) {
qCritical() << Q_FUNC_INFO << "Exception caught:" << e.message();
} catch (...) {
diff --git a/src/libs/installer/fileutils.h b/src/libs/installer/fileutils.h
index 8b79ce052..304569ec5 100644
--- a/src/libs/installer/fileutils.h
+++ b/src/libs/installer/fileutils.h
@@ -49,12 +49,12 @@ enum DefaultFilePermissions {
Executable = 0x7755
};
-class INSTALLER_EXPORT TempDirDeleter
+class INSTALLER_EXPORT TempPathDeleter
{
public:
- explicit TempDirDeleter(const QString &path);
- explicit TempDirDeleter(const QStringList &paths = QStringList());
- ~TempDirDeleter();
+ explicit TempPathDeleter(const QString &path);
+ explicit TempPathDeleter(const QStringList &paths = QStringList());
+ ~TempPathDeleter();
QStringList paths() const;
@@ -65,7 +65,7 @@ public:
void releaseAndDelete(const QString &path);
private:
- Q_DISABLE_COPY(TempDirDeleter)
+ Q_DISABLE_COPY(TempPathDeleter)
QSet<QString> m_paths;
};
diff --git a/src/libs/installer/genericdatacache.cpp b/src/libs/installer/genericdatacache.cpp
new file mode 100644
index 000000000..efb3c3776
--- /dev/null
+++ b/src/libs/installer/genericdatacache.cpp
@@ -0,0 +1,619 @@
+/**************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "genericdatacache.h"
+
+#include "errors.h"
+#include "fileutils.h"
+#include "globals.h"
+#include "metadata.h"
+#include "updater.h"
+
+#include <QDir>
+#include <QDirIterator>
+#include <QtConcurrent>
+
+namespace QInstaller {
+
+static const QLatin1String scManifestFile("manifest.json");
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::CacheableItem
+ \brief The CacheableItem is a pure virtual class that defines an interface for
+ a type suited for storage with the \l{GenericDataCache} class.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::path() const
+
+ Returns the path of this item. A subclass may override this method.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::setPath(const QString &path)
+
+ Sets the path of the item to \a path. A subclass may override this method.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::checksum() const
+
+ Returns the checksum of this item. A subclass must implement this method.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::isValid() const
+
+ Returns \c true if this item is valid, \c false otherwise.
+ A subclass must implement this method.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::isActive const
+
+ Returns \c true if this item is an actively used cache item, \c false otherwise.
+ This information is used as a hint for filtering obsolete entries, an active item
+ can never be obsolete.
+
+ A subclass must implement this method.
+*/
+
+/*!
+ \fn QInstaller::CacheableItem::obsoletes(CacheableItem *other)
+
+ Returns \c true if the calling item obsoletes \a other item, \c false otherwise.
+ This method is used for filtering obsolete entries from the cache.
+
+ A subclass must implement this method.
+*/
+
+/*!
+ Virtual destructor for \c CacheableItem.
+*/
+CacheableItem::~CacheableItem()
+{
+}
+
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::GenericDataCache
+ \brief The GenericDataCache is a template class for a checksum based storage
+ of items on disk.
+
+ GenericDataCache\<T\> manages a cache storage for a set \l{path()}, which contains
+ a subdirectory for each registered item. An item of type \c T should implement
+ methods declared in the \l{CacheableItem} interface. The GenericDataCache\<T\> class can
+ still be explicitly specialized to use the derived type as a template argument, to
+ allow retrieving items as the derived type without casting.
+*/
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::GenericDataCache()
+
+ Constructs a new empty cache. The cache is invalid until set with a
+ path and initialized.
+*/
+template <typename T>
+GenericDataCache<T>::GenericDataCache()
+ : m_version(QLatin1String("1.0.0"))
+ , m_invalidated(true)
+{
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::GenericDataCache(const QString &path, const QString &type, const QString &version)
+
+ Constructs a cache to \a path with the given \a type and \a version.
+ The cache is initialized automatically.
+*/
+template <typename T>
+GenericDataCache<T>::GenericDataCache(const QString &path, const QString &type,
+ const QString &version)
+ : m_path(path)
+ , m_type(type)
+ , m_version(version)
+ , m_invalidated(true)
+{
+ initialize();
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::~GenericDataCache()
+
+ Deletes the cache object. Item contents on disk are kept.
+*/
+template <typename T>
+GenericDataCache<T>::~GenericDataCache()
+{
+ if (m_invalidated)
+ return;
+
+ toDisk();
+ invalidate();
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::setType(const QString &type)
+
+ Sets the name of the wrapped type to \a type. This is used for determining if an
+ existing cache holds items of the same type. Trying to load cached items with mismatching
+ type results in discarding the old items. Optional.
+*/
+template<typename T>
+void GenericDataCache<T>::setType(const QString &type)
+{
+ m_type = type;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::setVersion(const QString &version)
+
+ Sets the version of the cache to \a version. Loading from a cache with different
+ expected version discards the old items. The version property defaults to \c{1.0.0}.
+*/
+template<typename T>
+void GenericDataCache<T>::setVersion(const QString &version)
+{
+ m_version = version;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::initialize()
+
+ Initializes a cache. Creates a new directory for the path configured for
+ this cache if it does not exist, and loads any previously cached items from
+ the directory. The cache directory is locked for access by this process only.
+ Returns \c true on success, \c false otherwise.
+*/
+template <typename T>
+bool GenericDataCache<T>::initialize()
+{
+ Q_ASSERT(m_items.isEmpty());
+
+ if (m_path.isEmpty()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot initialize cache with empty path."));
+ return false;
+ }
+
+ QDir directory(m_path);
+ if (!directory.exists() && !directory.mkpath(m_path)) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot create directory \"%1\" for cache.").arg(m_path));
+ return false;
+ }
+
+ if (m_lock && !m_lock->unlock()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot initialize cache: %1").arg(m_lock->errorString()));
+ return false;
+ }
+
+ m_lock.reset(new KDUpdater::LockFile(m_path + QLatin1String("/cache.lock")));
+ if (!m_lock->lock()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot initialize cache: %1").arg(m_lock->errorString()));
+ return false;
+ }
+
+ if (!fromDisk())
+ return false;
+
+ m_invalidated = false;
+ return true;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::clear()
+
+ Removes all items from the cache and deletes their contents on disk. If
+ the cache directory becomes empty, it is also deleted. The cache becomes
+ invalid after this action, even in case of error while clearing. In that
+ case already deleted items will be lost. Returns \c true on success,
+ \c false otherwise.
+*/
+template <typename T>
+bool GenericDataCache<T>::clear()
+{
+ if (m_invalidated) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot clear invalidated cache."));
+ return false;
+ }
+
+ QFile manifestFile(m_path + QDir::separator() + scManifestFile);
+ if (manifestFile.exists() && !manifestFile.remove()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot remove manifest file: %1").arg(manifestFile.errorString()));
+ invalidate();
+ return false;
+ }
+
+ for (T *item : qAsConst(m_items)) {
+ try {
+ QInstaller::removeDirectory(item->path());
+ } catch (const Error &e) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Error while clearing cache: %1").arg(e.message()));
+ }
+ }
+
+ invalidate();
+ QDir().rmdir(m_path);
+ return true;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::isValid() const
+
+ Returns \c true if the cache is valid, \c false otherwise. A cache is considered
+ valid when it is initialized to a set path.
+*/
+template<typename T>
+bool GenericDataCache<T>::isValid() const
+{
+ return !m_invalidated;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::errorString() const
+
+ Returns a string representing the last error with the cache.
+*/
+template<typename T>
+QString GenericDataCache<T>::errorString() const
+{
+ return m_error;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::path() const
+
+ Returns the path of the cache on disk.
+*/
+template <typename T>
+QString GenericDataCache<T>::path() const
+{
+ return m_path;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::setPath(const QString &path)
+
+ Sets a new \a path for the cache and invalidates current items. Saves the information
+ of the old cache to its manifest file.
+*/
+template <typename T>
+void GenericDataCache<T>::setPath(const QString &path)
+{
+ if (!m_invalidated)
+ toDisk();
+
+ m_path = path;
+ invalidate();
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::items() const
+
+ Returns a list of cached items.
+*/
+template <typename T>
+QList<T *> GenericDataCache<T>::items() const
+{
+ if (m_invalidated) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot retrieve items from invalidated cache."));
+ return QList<T *>();
+ }
+ return m_items.values();
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::itemByChecksum(const QByteArray &checksum) const
+
+ Returns an item that matches the \a checksum or \c nullptr in case
+ no such item is cached.
+*/
+template <typename T>
+T *GenericDataCache<T>::itemByChecksum(const QByteArray &checksum) const
+{
+ if (m_invalidated) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot retrieve item from invalidated cache."));
+ return nullptr;
+ }
+ return m_items.value(checksum);
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::itemByPath(const QString &path) const
+
+ Returns an item from the \a path or \c nullptr in case no such item
+ is cached. Depending on the size of the cache, this can be much
+ slower than retrieving an item with \l{itemByChecksum()}.
+*/
+template <typename T>
+T *GenericDataCache<T>::itemByPath(const QString &path) const
+{
+ auto it = std::find_if(m_items.constBegin(), m_items.constEnd(),
+ [&](T *item) {
+ return (QDir::fromNativeSeparators(path) == QDir::fromNativeSeparators(item->path()));
+ }
+ );
+ if (it != m_items.constEnd())
+ return it.value();
+
+ return nullptr;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::registerItem(T *item, bool replace)
+
+ Registers the \a item to the cache. If \a replace is set to \c true,
+ the new \a item replaces a previous item with the same checksum.
+
+ The cache takes ownership of the object pointed by \a item. The contents of the
+ item are copied to the cache with a subdirectory name that matches the checksum
+ of the item.
+
+ Returns \c true on success or \c false if the item could not be registered.
+*/
+template <typename T>
+bool GenericDataCache<T>::registerItem(T *item, bool replace)
+{
+ if (m_invalidated) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot register item to invalidated cache."));
+ return false;
+ }
+ if (!item) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot register null item."));
+ return false;
+ }
+ if (!item->isValid()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot register invalid item with checksum %1").arg(QLatin1String(item->checksum())));
+ return false;
+ }
+ if (m_items.contains(item->checksum())) {
+ if (replace) {// replace existing item including contents on disk
+ removeItem(item->checksum());
+ } else {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot register item with checksum %1. An item with the same checksum "
+ "already exists in cache.").arg(QLatin1String(item->checksum())));
+ return false;
+ }
+ }
+
+ const QString newPath = m_path + QDir::separator() + QString::fromLatin1(item->checksum());
+ try {
+ // A directory is in the way but it isn't registered to the current cache, remove.
+ if (QDir().exists(newPath))
+ QInstaller::removeDirectory(newPath);
+
+ QInstaller::copyDirectoryContents(item->path(), newPath);
+ } catch (const Error &e) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Error while copying item to path \"%1\": %2").arg(newPath, e.message()));
+ return false;
+ }
+
+ item->setPath(newPath);
+ if (item->isValid()) {
+ m_items.insert(item->checksum(), item);
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::removeItem(const QByteArray &checksum)
+
+ Removes the item specified by \a checksum from the cache and deletes the
+ contents of the item from disk. Returns \c true if the item
+ was removed successfully, \c false otherwise.
+*/
+template <typename T>
+bool GenericDataCache<T>::removeItem(const QByteArray &checksum)
+{
+ if (m_invalidated) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot remove item from invalidated cache."));
+ return false;
+ }
+ QScopedPointer<T> item(m_items.take(checksum));
+ if (!item) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot remove item specified by checksum %1: no such item exists.").arg(QLatin1String(checksum)));
+ return false;
+ }
+
+ try {
+ QInstaller::removeDirectory(item->path());
+ } catch (const Error &e) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Error while removing directory \"%1\": %2").arg(item->path(), e.message()));
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::obsoleteItems() const
+
+ Returns items considered obsolete from the cache.
+*/
+template<typename T>
+QList<T *> GenericDataCache<T>::obsoleteItems() const
+{
+ const QList<T *> obsoletes = QtConcurrent::blockingFiltered(m_items.values(),
+ [&](T *item1) {
+ if (item1->isActive()) // We can skip the iteration for active entries
+ return false;
+
+ for (T *item2 : qAsConst(m_items)) {
+ if (item2->obsoletes(item1))
+ return true;
+ }
+ return false;
+ }
+ );
+ return obsoletes;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::invalidate()
+
+ Marks the cache invalid and clears all items. The contents
+ on disk are not deleted. Releases the lock file of the cache.
+*/
+template <typename T>
+void GenericDataCache<T>::invalidate()
+{
+ if (!m_items.isEmpty()) {
+ qDeleteAll(m_items);
+ m_items.clear();
+ }
+ if (m_lock && !m_lock->unlock()) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Error while invalidating cache: %1").arg(m_lock->errorString()));
+ }
+ m_invalidated = true;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::setErrorString(const QString &error)
+
+ Sets the current error string to \a error and prints it as a warning to the console.
+*/
+template<typename T>
+void GenericDataCache<T>::setErrorString(const QString &error) const
+{
+ m_error = error;
+ qCWarning(QInstaller::lcInstallerInstallLog) << error;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::fromDisk()
+
+ Reads the manifest file of the cache if one exists, and populates the internal
+ hash from the file contents. Returns \c true if the manifests was read successfully
+ or if the reading was omitted. This is the case if the file does not exist yet, or
+ the type or version of the manifest does not match the current cache object. In case
+ of mismatch the old items are not restored. Returns \c false otherwise.
+*/
+template<typename T>
+bool GenericDataCache<T>::fromDisk()
+{
+ QFile manifestFile(m_path + QDir::separator() + scManifestFile);
+ if (!manifestFile.exists()) // Not yet created
+ return true;
+
+ if (!manifestFile.open(QIODevice::ReadOnly)) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot open manifest file: %1").arg(manifestFile.errorString()));
+ return false;
+ }
+
+ const QByteArray manifestData = manifestFile.readAll();
+ const QJsonDocument manifestJsonDoc(QJsonDocument::fromJson(manifestData));
+ const QJsonObject docJsonObject = manifestJsonDoc.object();
+
+ const QJsonValue type = docJsonObject.value(QLatin1String("type"));
+ if (type.toString() != m_type) {
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Discarding existing items from cache of type:"
+ << type.toString() << ". New type:" << m_type;
+ return true;
+ }
+
+ const QJsonValue version = docJsonObject.value(QLatin1String("version"));
+ if (KDUpdater::compareVersion(version.toString(), m_version) != 0) {
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Discarding existing items from cache with version:"
+ << version.toString() << ". New version:" << m_version;
+ return true;
+ }
+
+ const QJsonArray itemsJsonArray = docJsonObject.value(QLatin1String("items")).toArray();
+ for (const auto &itemJsonValue : itemsJsonArray) {
+ const QString checksum = itemJsonValue.toString();
+
+ QScopedPointer<T> item(new T(m_path + QDir::separator() + checksum));
+ m_items.insert(checksum.toLatin1(), item.take());
+
+ // The cache directory may contain other entries (unrelated directories or
+ // invalid old cache items) which we don't care about, unless registering
+ // a new entry requires overwriting them.
+ }
+ return true;
+}
+
+/*!
+ \fn template <typename T> QInstaller::GenericDataCache<T>::toDisk()
+
+ Writes the manifest file with the contents of the internal item hash.
+ Returns \c true on success, \c false otherwise.
+*/
+template<typename T>
+bool GenericDataCache<T>::toDisk()
+{
+ QFile manifestFile(m_path + QDir::separator() + scManifestFile);
+ if (!manifestFile.open(QIODevice::WriteOnly)) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot open manifest file: %1").arg(manifestFile.errorString()));
+ return false;
+ }
+
+ QJsonArray itemsJsonArray;
+ const QList<QByteArray> keys = m_items.keys();
+ for (const QByteArray &key : keys)
+ itemsJsonArray.append(QJsonValue(QLatin1String(key)));
+
+ QJsonObject docJsonObject;
+ docJsonObject.insert(QLatin1String("items"), itemsJsonArray);
+ docJsonObject.insert(QLatin1String("version"), m_version);
+ if (!m_type.isEmpty())
+ docJsonObject.insert(QLatin1String("type"), m_type);
+
+ QJsonDocument manifestJsonDoc;
+ manifestJsonDoc.setObject(docJsonObject);
+ if (manifestFile.write(manifestJsonDoc.toJson()) == -1) {
+ setErrorString(QCoreApplication::translate("GenericDataCache",
+ "Cannot write contents for manifest file: %1").arg(manifestFile.errorString()));
+ return false;
+ }
+ return true;
+}
+
+template class GenericDataCache<Metadata>;
+
+} // namespace QInstaller
diff --git a/src/libs/installer/genericdatacache.h b/src/libs/installer/genericdatacache.h
new file mode 100644
index 000000000..7b909cc7a
--- /dev/null
+++ b/src/libs/installer/genericdatacache.h
@@ -0,0 +1,111 @@
+/**************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef GENERICDATACACHE_H
+#define GENERICDATACACHE_H
+
+#include "installer_global.h"
+#include "lockfile.h"
+
+#include <QHash>
+#include <QScopedPointer>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT CacheableItem
+{
+public:
+ CacheableItem() = default;
+ explicit CacheableItem(const QString &path)
+ : m_path(path)
+ {}
+ virtual ~CacheableItem() = 0;
+
+ virtual QString path() const { return m_path; }
+ virtual void setPath(const QString &path) { m_path = path; }
+
+ virtual QByteArray checksum() const = 0;
+ virtual bool isValid() const = 0;
+
+ virtual bool isActive() const = 0;
+ virtual bool obsoletes(CacheableItem *other) = 0;
+
+private:
+ QString m_path;
+};
+
+template <typename T>
+class INSTALLER_EXPORT GenericDataCache
+{
+public:
+ GenericDataCache();
+ explicit GenericDataCache(const QString &path, const QString &type, const QString &version);
+ ~GenericDataCache();
+
+ void setType(const QString &type);
+ void setVersion(const QString &version);
+
+ bool initialize();
+ bool clear();
+
+ bool isValid() const;
+ QString errorString() const;
+
+ QString path() const;
+ void setPath(const QString &path);
+
+ QList<T *> items() const;
+ T *itemByChecksum(const QByteArray &checksum) const;
+ T *itemByPath(const QString &path) const;
+
+ bool registerItem(T *item, bool replace = false);
+ bool removeItem(const QByteArray &checksum);
+
+ QList<T *> obsoleteItems() const;
+
+private:
+ void invalidate();
+ void setErrorString(const QString &error) const;
+
+ bool fromDisk();
+ bool toDisk();
+
+private:
+ QScopedPointer<KDUpdater::LockFile> m_lock;
+ QHash<QByteArray, T *> m_items;
+ QString m_path;
+ QString m_type;
+ QString m_version;
+ mutable QString m_error;
+
+ bool m_invalidated;
+};
+
+} // namespace QInstaller
+
+#endif // GENERICDATACACHE_H
diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro
index 6eebed33b..316d2abfd 100644
--- a/src/libs/installer/installer.pro
+++ b/src/libs/installer/installer.pro
@@ -45,7 +45,9 @@ HEADERS += packagemanagercore.h \
aspectratiolabel.h \
componentsortfilterproxymodel.h \
concurrentoperationrunner.h \
+ genericdatacache.h \
loggingutils.h \
+ metadata.h \
packagemanagercore_p.h \
packagemanagergui.h \
binaryformat.h \
@@ -148,7 +150,9 @@ SOURCES += packagemanagercore.cpp \
directoryguard.cpp \
fileguard.cpp \
componentsortfilterproxymodel.cpp \
+ genericdatacache.cpp \
loggingutils.cpp \
+ metadata.cpp \
operationtracer.cpp \
packagemanagercore_p.cpp \
packagemanagergui.cpp \
diff --git a/src/libs/installer/metadata.cpp b/src/libs/installer/metadata.cpp
new file mode 100644
index 000000000..b74ffc3f1
--- /dev/null
+++ b/src/libs/installer/metadata.cpp
@@ -0,0 +1,257 @@
+/**************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "metadata.h"
+
+#include "constants.h"
+#include "globals.h"
+#include "metadatajob.h"
+
+#include <QCryptographicHash>
+#include <QDir>
+#include <QDomDocument>
+#include <QFile>
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::Metadata
+ \brief The Metadata class represents fetched metadata from a repository.
+*/
+
+/*!
+ Constructs a new metadata object.
+*/
+Metadata::Metadata()
+ : CacheableItem()
+ , m_fromDefaultRepository(false)
+{
+}
+
+/*!
+ Constructs a new metadata object with a \a path.
+*/
+Metadata::Metadata(const QString &path)
+ : CacheableItem(path)
+ , m_fromDefaultRepository(false)
+{
+}
+
+/*!
+ Returns the checksum of this metadata which is the checksum of the Updates.xml file.
+ The checksum value is stored to memory after first read, so a single object should
+ not be reused for referring other metadata.
+*/
+QByteArray Metadata::checksum() const
+{
+ if (!m_checksum.isEmpty())
+ return m_checksum;
+
+ QFile updateFile(path() + QLatin1String("/Updates.xml"));
+ if (!updateFile.open(QIODevice::ReadOnly))
+ return QByteArray();
+
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(&updateFile);
+ m_checksum = hash.result().toHex();
+
+ return m_checksum;
+}
+
+/*!
+ Sets the checksum of this metadata to \a checksum. Calling this function
+ will omit calculating the checksum from the update file when retrieving
+ the checksum with \l{checksum()} for the first time.
+*/
+void Metadata::setChecksum(const QByteArray &checksum)
+{
+ m_checksum = checksum;
+}
+
+/*!
+ Returns the root of the document tree representing the \c Updates.xml
+ document of this metadata. Returns an empty \c QDomDocument in case
+ of failure to reading the file.
+*/
+QDomDocument Metadata::updatesDocument() const
+{
+ QFile updateFile(path() + QLatin1String("/Updates.xml"));
+ if (!updateFile.open(QIODevice::ReadOnly)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot open" << updateFile.fileName()
+ << "for reading:" << updateFile.errorString();
+ return QDomDocument();
+ }
+
+ QDomDocument doc;
+ QString errorString;
+ if (!doc.setContent(&updateFile, &errorString)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot set document content:" << errorString;
+ return QDomDocument();
+ }
+
+ return doc;
+}
+
+/*!
+ Returns \c true if the \c Updates.xml document of this metadata
+ exists and can be opened for reading, \c false otherwise.
+*/
+bool Metadata::isValid() const
+{
+ QFile updateFile(path() + QLatin1String("/Updates.xml"));
+ if (!updateFile.open(QIODevice::ReadOnly)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot open" << updateFile.fileName()
+ << "for reading:" << updateFile.errorString();
+ return false;
+ }
+ return true;
+}
+
+/*!
+ Returns \c true if this metadata is active, \c false otherwise. Metadata is
+ considered active if it is currently associated with a valid repository.
+*/
+bool Metadata::isActive() const
+{
+ return m_repository.isValid();
+}
+
+/*!
+ Checks whether this metadata object obsoletes the \a other metadata. The other metadata
+ is considered obsolete if it is not currently associated with any repository, and the
+ URL of the calling metadata matches the last known URL of the other metadata. Returns
+ \c true if the current metadata obsoletes the other, \c false otherwise.
+*/
+bool Metadata::obsoletes(CacheableItem *other)
+{
+ if (Metadata *meta = dynamic_cast<Metadata *>(other)) {
+ // If the current metadata is not in use it should not replace anything.
+ if (!isActive())
+ return false;
+
+ // If the other metadata is in use it is not obsolete.
+ if (meta->isActive())
+ return false;
+
+ // Current metadata has the same persistent url as other metadata, other is obsolete.
+ if (persistentRepositoryPath() == meta->persistentRepositoryPath())
+ return true;
+
+ // The refreshed url of the current metadata matches the persistent url of other
+ // metadata, other is obsolete.
+ if (m_repository.url().path(QUrl::FullyEncoded) == meta->persistentRepositoryPath())
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Returns the repository object set for this metadata. This is the repository
+ the metadata is currently associated with, which may be different from the
+ repository where it was originally fetched.
+*/
+Repository Metadata::repository() const
+{
+ return m_repository;
+}
+
+/*!
+ Sets the \a repository object of this metadata. If a repository
+ is already set, the new one will override the previous one. The metadata becomes
+ associated with the set repository even if it was fetched from another one.
+*/
+void Metadata::setRepository(Repository repository)
+{
+ m_repository = repository;
+}
+
+/*!
+ Returns \c true if this metadata is available from a default repository,
+ which means a repository without category, \c false otherwise.
+*/
+bool Metadata::isAvailableFromDefaultRepository() const
+{
+ return m_fromDefaultRepository;
+}
+
+/*!
+ Sets the metadata available from a default repository based on the value
+ of \a defaultRepository. This is not mutually exclusive from a metadata
+ that has repository categories set.
+*/
+void Metadata::setAvailableFromDefaultRepository(bool defaultRepository)
+{
+ m_fromDefaultRepository = defaultRepository;
+}
+
+/*!
+ Sets the repository path of this metadata from \a url, without the protocol or hostname.
+ Unlike \l{setRepository()} this value is saved to disk, which allows retrieving the
+ repository path of the metadata on later runs.
+*/
+void Metadata::setPersistentRepositoryPath(const QUrl &url)
+{
+ const QString newPath = url.path(QUrl::FullyEncoded).trimmed();
+ if (m_persistentRepositoryPath == newPath)
+ return;
+
+ QFile file(path() + QLatin1String("/repository.txt"));
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot open" << file.fileName() << "for writing:" << file.errorString();
+ return;
+ }
+ QTextStream out(&file);
+ out << newPath;
+
+ m_persistentRepositoryPath = newPath;
+}
+
+/*!
+ Returns the persistent repository path of the metadata.
+*/
+QString Metadata::persistentRepositoryPath()
+{
+ if (!m_persistentRepositoryPath.isEmpty())
+ return m_persistentRepositoryPath;
+
+ QFile file(path() + QLatin1String("/repository.txt"));
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot open" << file.fileName() << "for reading:" << file.errorString();
+ return QString();
+ }
+ m_persistentRepositoryPath = QString::fromLatin1(file.readAll()).trimmed();
+ return m_persistentRepositoryPath;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/metadata.h b/src/libs/installer/metadata.h
new file mode 100644
index 000000000..c2afe7df6
--- /dev/null
+++ b/src/libs/installer/metadata.h
@@ -0,0 +1,81 @@
+/**************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef METADATA_H
+#define METADATA_H
+
+#include "installer_global.h"
+#include "genericdatacache.h"
+#include "repository.h"
+
+#include <QDomDocument>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT Metadata : public CacheableItem
+{
+public:
+ Metadata();
+ explicit Metadata(const QString &path);
+ ~Metadata() {}
+
+ QByteArray checksum() const override;
+ void setChecksum(const QByteArray &checksum);
+ QDomDocument updatesDocument() const;
+
+ bool isValid() const override;
+ bool isActive() const override;
+ bool obsoletes(CacheableItem *other) override;
+
+ Repository repository() const;
+ void setRepository(Repository repository);
+
+ bool isAvailableFromDefaultRepository() const;
+ void setAvailableFromDefaultRepository(bool defaultRepository);
+
+ void setPersistentRepositoryPath(const QUrl &url);
+ QString persistentRepositoryPath();
+
+private:
+ Repository m_repository;
+ QString m_persistentRepositoryPath;
+ mutable QByteArray m_checksum;
+
+ bool m_fromDefaultRepository;
+};
+
+Q_GLOBAL_STATIC_WITH_ARGS(QStringList, scMetaElements, (QStringList(
+ QLatin1String("Script")) <<
+ QLatin1String("Licenses") <<
+ QLatin1String("UserInterfaces") <<
+ QLatin1String("Translations")
+));
+
+} // namespace QInstaller
+
+#endif // METADATA_H
diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp
index 50fd723ab..62bd747f4 100644
--- a/src/libs/installer/metadatajob.cpp
+++ b/src/libs/installer/metadatajob.cpp
@@ -38,26 +38,16 @@
#include "globals.h"
#include <QTemporaryDir>
+#include <QtConcurrent>
#include <QtMath>
#include <QRandomGenerator>
-const QStringList metaElements = {QLatin1String("Script"), QLatin1String("Licenses"), QLatin1String("UserInterfaces"), QLatin1String("Translations")};
+#define QUOTE_(x) #x
+#define QUOTE(x) QUOTE_(x)
namespace QInstaller {
/*!
- \inmodule QtInstallerFramework
- \class QInstaller::Metadata
- \internal
-*/
-
-/*!
- \inmodule QtInstallerFramework
- \class QInstaller::ArchiveMetadata
- \internal
-*/
-
-/*!
\enum QInstaller::DownloadType
\value All
@@ -97,6 +87,7 @@ MetadataJob::MetadataJob(QObject *parent)
, m_downloadType(DownloadType::All)
, m_downloadableChunkSize(1000)
, m_taskNumber(0)
+ , m_defaultRepositoriesFetched(false)
{
QByteArray downloadableChunkSize = qgetenv("IFW_METADATA_SIZE");
if (!downloadableChunkSize.isEmpty()) {
@@ -123,26 +114,92 @@ MetadataJob::~MetadataJob()
* repositories which might not be currently selected.
*/
-QList<Metadata> MetadataJob::metadata() const
+QList<Metadata *> MetadataJob::metadata() const
{
- QList<Metadata> metadata = m_metaFromDefaultRepositories.values();
- foreach (RepositoryCategory repositoryCategory, m_core->settings().repositoryCategories()) {
- if (m_core->isUpdater() || (repositoryCategory.isEnabled() && m_fetchedArchive.contains(repositoryCategory.displayname()))) {
- QList<ArchiveMetadata> archiveMetaList = m_fetchedArchive.values(repositoryCategory.displayname());
- foreach (ArchiveMetadata archiveMeta, archiveMetaList) {
- metadata.append(archiveMeta.metaData);
- }
+ const QSet<RepositoryCategory> categories = m_core->settings().repositoryCategories();
+ QHash<RepositoryCategory, QSet<Repository>> repositoryHash;
+ // Create hash of categorized repositories to avoid constructing
+ // excess temp objects when filtering below.
+ for (const RepositoryCategory &category : categories)
+ repositoryHash.insert(category, category.repositories());
+
+ QList<Metadata *> metadata = m_metaFromCache.items();
+ // Filter cache items not associated with current repositories and categories
+ QtConcurrent::blockingFilter(metadata, [&](const Metadata *item) {
+ if (!item->isActive())
+ return false;
+
+ // No need to check if the repository is enabled here. Changing the network
+ // settings resets the cache and we don't fetch the disabled repositories,
+ // so the cached items stay inactive.
+
+ if (item->isAvailableFromDefaultRepository())
+ return true;
+
+ QHash<RepositoryCategory, QSet<Repository>>::const_iterator it;
+ for (it = repositoryHash.constBegin(); it != repositoryHash.constEnd(); ++it) {
+ if (m_core->isUpdater())
+ return true;
+
+ if (!it.key().isEnabled())
+ continue; // Let's try the next one
+
+ if (it->contains(item->repository()))
+ return true;
}
- }
+ return false;
+ });
+
return metadata;
}
-Repository MetadataJob::repositoryForDirectory(const QString &directory) const
+/*
+ Returns a repository object from the cache item matching \a directory. If the
+ \a directory does not belong to the cache, an empty repository is returned.
+*/
+Repository MetadataJob::repositoryForCacheDirectory(const QString &directory) const
+{
+ QDir dir(directory);
+ if (!QDir::fromNativeSeparators(dir.path())
+ .startsWith(QDir::fromNativeSeparators(m_metaFromCache.path()))) {
+ return Repository();
+ }
+ const QString dirName = dir.dirName();
+ Metadata *cachedMeta = m_metaFromCache.itemByChecksum(dirName.toUtf8());
+ if (cachedMeta)
+ return cachedMeta->repository();
+
+ return Repository();
+}
+
+bool MetadataJob::resetCache(bool init)
+{
+ // Need the path from current settings
+ if (!m_core) {
+ qCWarning(lcInstallerInstallLog) << "Cannot reset metadata cache: "
+ "missing package manager core engine.";
+ return false;
+ }
+ m_metaFromCache.setPath(m_core->settings().localCachePath());
+ m_metaFromCache.setType(QLatin1String("Metadata"));
+ m_metaFromCache.setVersion(QLatin1String(QUOTE(IFW_REPOSITORY_FORMAT_VERSION)));
+
+ if (!init)
+ return true;
+
+ const bool success = m_metaFromCache.initialize();
+ if (success) {
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Using metadata cache from"
+ << m_metaFromCache.path();
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Found"
+ << m_metaFromCache.items().count() << "cached items.";
+ }
+ return success;
+}
+
+bool MetadataJob::clearCache()
{
- if (m_metaFromDefaultRepositories.contains(directory))
- return m_metaFromDefaultRepositories.value(directory).repository;
- else
- return m_metaFromArchive.value(directory).repository;
+ return m_metaFromCache.clear();
}
// -- private slots
@@ -151,13 +208,19 @@ void MetadataJob::doStart()
{
setError(Job::NoError);
setErrorString(QString());
+ m_metadataResult.clear();
if (!m_core) {
emitFinishedWithError(Job::Canceled, tr("Missing package manager core engine."));
return; // We can't do anything here without core, so avoid tons of !m_core checks.
}
+ if (!m_metaFromCache.isValid() && !resetCache(true)) {
+ emitFinishedWithError(JobError::CacheError, m_metaFromCache.errorString());
+ return;
+ }
+
const ProductKeyCheck *const productKeyCheck = ProductKeyCheck::instance();
if (m_downloadType == DownloadType::All || m_downloadType == DownloadType::UpdatesXML) {
- emit infoMessage(this, tr("Preparing meta information download..."));
+ emit infoMessage(this, tr("Fetching latest update information..."));
const bool onlineInstaller = m_core->isInstaller() && !m_core->isOfflineOnly();
if (onlineInstaller || m_core->isMaintainer()) {
QList<FileTaskItem> items;
@@ -226,6 +289,7 @@ void MetadataJob::startXMLTask(const QList<FileTaskItem> &items)
{
DownloadFileTask *const xmlTask = new DownloadFileTask(items);
xmlTask->setProxyFactory(m_core->proxyFactory());
+ setProgressTotalAmount(100);
connect(&m_xmlTask, &QFutureWatcher<FileTaskResult>::progressValueChanged, this,
&MetadataJob::progressChanged);
m_xmlTask.setFuture(QtConcurrent::run(&DownloadFileTask::doTask, xmlTask));
@@ -234,6 +298,7 @@ void MetadataJob::startXMLTask(const QList<FileTaskItem> &items)
void MetadataJob::doCancel()
{
reset();
+ resetCache();
emitFinishedWithError(Job::Canceled, tr("Metadata download canceled."));
}
@@ -257,6 +322,48 @@ void MetadataJob::startUnzipRepositoryTask(const Repository &repo)
watcher->setFuture(QtConcurrent::run(&UnzipArchiveTask::doTask, task));
}
+bool MetadataJob::updateCache()
+{
+ const int toRegisterCount = m_fetchedMetadata.count();
+ if (toRegisterCount > 0)
+ emit infoMessage(this, tr("Updating local cache with %1 new items...").arg(toRegisterCount));
+
+ // Register items from current run to cache
+ QStringList registeredKeys;
+ for (auto *meta : qAsConst(m_fetchedMetadata)) {
+ if (!m_metaFromCache.registerItem(meta)) {
+ emitFinishedWithError(QInstaller::CacheError, m_metaFromCache.errorString()
+ + tr(" Clearing the cache directory and restarting the application may solve this."));
+ return false;
+ }
+ meta->setPersistentRepositoryPath(meta->repository().url());
+ if (!m_core->settings().persistentLocalCache())
+ m_tempDirDeleter.add(meta->path());
+
+ registeredKeys.append(m_fetchedMetadata.key(meta));
+ }
+ // Remove items whose ownership was transferred to cache
+ for (auto &key : qAsConst(registeredKeys))
+ m_fetchedMetadata.remove(key);
+
+ // ...and clean up obsolete cached items
+ const QList<Metadata *> obsolete = m_metaFromCache.obsoleteItems();
+ for (auto *meta : obsolete)
+ m_metaFromCache.removeItem(meta->checksum());
+
+ return true;
+}
+
+/*
+ Resets the repository information from all cache items, which
+ makes them inactive until associated with new repositories.
+*/
+void MetadataJob::resetCacheRepositories()
+{
+ for (auto *metaToReset : m_metaFromCache.items())
+ metaToReset->setRepository(Repository());
+}
+
void MetadataJob::unzipRepositoryTaskFinished()
{
QFutureWatcher<void> *watcher = static_cast<QFutureWatcher<void> *>(sender());
@@ -408,10 +515,15 @@ void MetadataJob::xmlTaskFinished()
if (status == XmlDownloadSuccess) {
if (m_downloadType != DownloadType::UpdatesXML) {
- if (!fetchMetaDataPackages())
- emitFinished();
+ if (!fetchMetaDataPackages()) {
+ // No new metadata packages to fetch, still need to update the cache
+ // for refreshed repositories.
+ if (updateCache())
+ emitFinished();
+ }
} else {
- emitFinished();
+ if (updateCache())
+ emitFinished();
}
} else if (status == XmlDownloadRetry) {
QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection);
@@ -442,6 +554,9 @@ void MetadataJob::unzipTaskFinished()
delete watcher;
if (m_unzipTasks.isEmpty()) {
+ if (!updateCache())
+ return;
+
setProcessedAmount(100);
emitFinished();
}
@@ -486,7 +601,8 @@ void MetadataJob::metadataTaskFinished()
watcher->setFuture(QtConcurrent::run(&UnzipArchiveTask::doTask, task));
}
} else {
- emitFinished();
+ if (updateCache())
+ emitFinished();
}
}
} catch (const TaskException &e) {
@@ -516,7 +632,6 @@ bool MetadataJob::fetchMetaDataPackages()
DownloadFileTask *const metadataTask = new DownloadFileTask(tempPackages);
metadataTask->setProxyFactory(m_core->proxyFactory());
m_metadataTask.setFuture(QtConcurrent::run(&DownloadFileTask::doTask, metadataTask));
- setProgressTotalAmount(100);
QString metaInformation;
if (m_totalTaskCount > 1)
metaInformation = tr("Retrieving meta information from remote repository... %1/%2 ").arg(m_taskNumber).arg(m_totalTaskCount);
@@ -531,9 +646,11 @@ bool MetadataJob::fetchMetaDataPackages()
void MetadataJob::reset()
{
m_packages.clear();
- m_metaFromDefaultRepositories.clear();
- m_metaFromArchive.clear();
- m_fetchedArchive.clear();
+ m_defaultRepositoriesFetched = false;
+ m_fetchedCategorizedRepositories.clear();
+
+ qDeleteAll(m_fetchedMetadata);
+ m_fetchedMetadata.clear();
setError(Job::NoError);
setErrorString(QString());
@@ -586,7 +703,6 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
if (result.target().isEmpty()) {
continue;
}
- Metadata metadata;
QTemporaryDir tmp(QDir::tempPath() + QLatin1String("/remoterepo-XXXXXX"));
if (!tmp.isValid()) {
qCWarning(QInstaller::lcInstallerInstallLog) << "Cannot create unique temporary directory.";
@@ -594,11 +710,11 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
}
tmp.setAutoRemove(false);
- metadata.directory = tmp.path();
- m_tempDirDeleter.add(metadata.directory);
+ QScopedPointer<Metadata> metadata(new Metadata(tmp.path()));
+ m_tempDirDeleter.add(metadata->path());
QFile file(result.target());
- if (!file.rename(metadata.directory + QLatin1String("/Updates.xml"))) {
+ if (!file.rename(metadata->path() + QLatin1String("/Updates.xml"))) {
qCWarning(QInstaller::lcInstallerInstallLog) << "Cannot rename target to Updates.xml:"
<< file.errorString();
return XmlDownloadFailure;
@@ -609,20 +725,66 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
<< file.errorString();
return XmlDownloadFailure;
}
+ const FileTaskItem item = result.value(TaskRole::TaskItem).value<FileTaskItem>();
+
+ // Check if we have cached the metadata for this repository already
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(&file);
+ const QByteArray updatesChecksum = hash.result().toHex();
+
+ Metadata *cachedMetadata = m_metaFromCache.itemByChecksum(updatesChecksum);
+ if (cachedMetadata) {
+ const Repository repository = item.value(TaskRole::UserRole).value<Repository>();
+ if (cachedMetadata->isValid() && !repository.isCompressed()) {
+ // Refresh repository information to cache. Same repository may appear in multiple
+ // categories and the metadata may be available from default repositories simultaneously.
+ cachedMetadata->setRepository(repository);
+ if (!repository.categoryname().isEmpty())
+ m_fetchedCategorizedRepositories.insert(repository); // For faster lookups
+ else
+ cachedMetadata->setAvailableFromDefaultRepository(true);
+
+ // Refresh also persistent information, the url of the repository may have changed
+ // from the last fetch.
+ cachedMetadata->setPersistentRepositoryPath(repository.url());
+
+ // search for additional repositories that we might need to check
+ QDomDocument doc = cachedMetadata->updatesDocument();
+ const MetadataJob::Status status
+ = parseRepositoryUpdates(doc.documentElement(), result, cachedMetadata);
+ if (status == XmlDownloadRetry) {
+ // The repository update may have removed or replaced current repositories,
+ // clear repository information from cached items and refresh on next fetch run.
+ resetCacheRepositories();
+ return status;
+ }
+
+ continue;
+ }
+ // Missing or corrupted files, or compressed repository which takes priority
+ // over remote repository. We will re-download and uncompress
+ // the metadata. Remove broken item from the cache.
+ if (!m_metaFromCache.removeItem(updatesChecksum)) {
+ qCWarning(lcInstallerInstallLog) << m_metaFromCache.errorString();
+ return XmlDownloadFailure;
+ }
+ }
+ metadata->setChecksum(updatesChecksum);
+
+ file.seek(0);
QString error;
QDomDocument doc;
if (!doc.setContent(&file, &error)) {
qCWarning(QInstaller::lcInstallerInstallLog).nospace() << "Cannot fetch a valid version of Updates.xml from repository "
- << metadata.repository.displayname() << ": " << error;
+ << metadata->repository().displayname() << ": " << error;
//If there are other repositories, try to use those
continue;
}
file.close();
- const FileTaskItem item = result.value(TaskRole::TaskItem).value<FileTaskItem>();
- metadata.repository = item.value(TaskRole::UserRole).value<Repository>();
- const bool online = !(metadata.repository.url().scheme()).isEmpty();
+ metadata->setRepository(item.value(TaskRole::UserRole).value<Repository>());
+ const bool online = !(metadata->repository().url().scheme()).isEmpty();
bool testCheckSum = true;
const QDomElement root = doc.documentElement();
@@ -634,14 +796,14 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
// all metadata inside one repository to a single 7z file. Fetch that
// instead of component specific meta 7z files.
const QDomNode sha1 = root.firstChildElement(scSHA1);
- QDomElement metadataNameElement = root.firstChildElement(QLatin1String("MetadataName"));
+ QDomElement metadataNameElement = root.firstChildElement(scMetadataName);
QDomNodeList children = root.childNodes();
if (!sha1.isNull() && !metadataNameElement.isNull()) {
- const QString repoUrl = metadata.repository.url().toString();
- const QString metadataName = metadataNameElement.toElement().text();
- addFileTaskItem(QString::fromLatin1("%1/%2").arg(repoUrl, metadataName),
- metadata.directory + QString::fromLatin1("/%1").arg(metadataName),
- metadata, sha1.toElement().text(), QString());
+ const QString repoUrl = metadata->repository().url().toString();
+ const QString metadataName = metadataNameElement.toElement().text();
+ addFileTaskItem(QString::fromLatin1("%1/%2").arg(repoUrl, metadataName),
+ metadata->path() + QString::fromLatin1("/%1").arg(metadataName),
+ metadata.get(), sha1.toElement().text(), QString());
} else {
bool metaFound = false;
for (int i = 0; i < children.count(); ++i) {
@@ -653,16 +815,13 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
online, testCheckSum);
// If meta element (script, licenses, etc.) is not found, no need to fetch metadata.
- // The offline-generator instance is an exception to this - if the Updates.xml contains
- // checksum element for the meta-archive, we will fetch it, so that the temporary
- // location contents match the remote repository.
- if (metaFound || (m_core->isOfflineGenerator() && !packageHash.isEmpty())) {
- const QString repoUrl = metadata.repository.url().toString();
+ if (metaFound) {
+ const QString repoUrl = metadata->repository().url().toString();
addFileTaskItem(QString::fromLatin1("%1/%2/%3meta.7z").arg(repoUrl, packageName, packageVersion),
- metadata.directory + QString::fromLatin1("/%1-%2-meta.7z").arg(packageName, packageVersion),
- metadata, packageHash, packageName);
+ metadata->path() + QString::fromLatin1("/%1-%2-meta.7z").arg(packageName, packageVersion),
+ metadata.get(), packageHash, packageName);
} else {
- QString fileName = metadata.directory + QLatin1Char('/') + packageName;
+ QString fileName = metadata->path() + QLatin1Char('/') + packageName;
QDir directory(fileName);
if (!directory.exists()) {
directory.mkdir(fileName);
@@ -672,40 +831,24 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
}
}
- if (metadata.repository.categoryname().isEmpty()) {
- m_metaFromDefaultRepositories.insert(metadata.directory, metadata);
- } else {
- //Hash metadata to help checking if meta for repository is already fetched
- ArchiveMetadata archiveMetadata;
- archiveMetadata.metaData = metadata;
- m_fetchedArchive.insert(metadata.repository.categoryname(), archiveMetadata);
-
- //Check if other categories have the same url (contains same metadata)
- //so we can speed up other category fetches
- foreach (RepositoryCategory category, m_core->settings().repositoryCategories()) {
- if (category.displayname() != metadata.repository.categoryname()) {
- foreach (Repository repository, category.repositories()) {
- if (repository.url() == metadata.repository.url()) {
- m_fetchedArchive.insert(category.displayname(), archiveMetadata);
- }
- }
- }
- }
- // Hash for faster lookups
- m_metaFromArchive.insert(metadata.directory, metadata);
- }
+ // Remember the fetched metadata
+ Metadata *const metadataPtr = metadata.get();
+ const QString categoryName = metadata->repository().categoryname();
+ if (categoryName.isEmpty())
+ metadata->setAvailableFromDefaultRepository(true);
+ else
+ m_fetchedCategorizedRepositories.insert(metadataPtr->repository()); // For faster lookups
+ const QString metadataPath = metadata->path();
+ m_fetchedMetadata.insert(metadataPath, metadata.take());
// search for additional repositories that we might need to check
- const QDomNode repositoryUpdate = root.firstChildElement(QLatin1String("RepositoryUpdate"));
- if (!repositoryUpdate.isNull()) {
- QHash<QString, QPair<Repository, Repository> > repositoryUpdates =
- searchAdditionalRepositories(repositoryUpdate, result, metadata);
- if (!repositoryUpdates.isEmpty()) {
- MetadataJob::Status status = setAdditionalRepositories(repositoryUpdates, result, metadata);
- if (status == XmlDownloadRetry)
- return status;
- }
+ const MetadataJob::Status status = parseRepositoryUpdates(root, result, metadataPtr);
+ if (status == XmlDownloadRetry) {
+ // The repository update may have removed or replaced current repositories,
+ // clear repository information from cached items and refresh on next fetch run.
+ resetCacheRepositories();
+ return status;
}
}
double taskCount = m_packages.length()/static_cast<double>(m_downloadableChunkSize);
@@ -715,46 +858,54 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
return XmlDownloadSuccess;
}
+MetadataJob::Status MetadataJob::parseRepositoryUpdates(const QDomElement &root,
+ const FileTaskResult &result, Metadata *metadata)
+{
+ MetadataJob::Status status = XmlDownloadSuccess;
+ const QDomNode repositoryUpdate = root.firstChildElement(QLatin1String("RepositoryUpdate"));
+ if (!repositoryUpdate.isNull()) {
+ const QHash<QString, QPair<Repository, Repository> > repositoryUpdates
+ = searchAdditionalRepositories(repositoryUpdate, result, *metadata);
+ if (!repositoryUpdates.isEmpty())
+ status = setAdditionalRepositories(repositoryUpdates, result, *metadata);
+ }
+ return status;
+}
+
QSet<Repository> MetadataJob::getRepositories()
{
QSet<Repository> repositories;
- //In the first run, m_metadata is empty. Get always the default repositories
- if (m_metaFromDefaultRepositories.isEmpty()) {
+ //In the first run, get always the default repositories
+ if (!m_defaultRepositoriesFetched) {
repositories = m_core->settings().repositories();
+ m_defaultRepositoriesFetched = true;
}
// Fetch repositories under archive which are selected in UI.
// If repository is already fetched, do not fetch it again.
// In updater mode, fetch always all archive repositories to get updates
- foreach (RepositoryCategory repositoryCategory, m_core->settings().repositoryCategories()) {
- if (m_core->isUpdater() || (repositoryCategory.isEnabled())) {
- foreach (Repository repository, repositoryCategory.repositories()) {
- QHashIterator<QString, ArchiveMetadata> i(m_fetchedArchive);
- bool fetch = true;
- while (i.hasNext()) {
- i.next();
- ArchiveMetadata metaData = i.value();
- if (repository.url() == metaData.metaData.repository.url())
- fetch = false;
- }
- if (fetch)
- repositories.insert(repository);
- }
- }
+ for (const RepositoryCategory &repositoryCategory : m_core->settings().repositoryCategories()) {
+ if (!m_core->isUpdater() && !repositoryCategory.isEnabled())
+ continue;
+
+ for (const Repository &repository : repositoryCategory.repositories()) {
+ if (!m_fetchedCategorizedRepositories.contains(repository))
+ repositories.insert(repository);
+ }
}
return repositories;
}
-void MetadataJob::addFileTaskItem(const QString &source, const QString &target, const Metadata &metadata,
+void MetadataJob::addFileTaskItem(const QString &source, const QString &target, Metadata *metadata,
const QString &sha1, const QString &packageName)
{
FileTaskItem item(source, target);
QAuthenticator authenticator;
- authenticator.setUser(metadata.repository.username());
- authenticator.setPassword(metadata.repository.password());
+ authenticator.setUser(metadata->repository().username());
+ authenticator.setPassword(metadata->repository().password());
- item.insert(TaskRole::UserRole, metadata.directory);
+ item.insert(TaskRole::UserRole, metadata->path());
item.insert(TaskRole::Checksum, sha1.toLatin1());
item.insert(TaskRole::Authenticator, QVariant::fromValue(authenticator));
item.insert(TaskRole::Name, packageName);
@@ -775,7 +926,7 @@ bool MetadataJob::parsePackageUpdate(const QDomNodeList &c2, QString &packageNam
else if ((element.tagName() == QLatin1String("SHA1")) && testCheckSum)
packageHash = element.text();
else {
- foreach (QString meta, metaElements) {
+ foreach (QString meta, *scMetaElements) {
if (element.tagName() == meta) {
metaFound = true;
break;
@@ -828,7 +979,7 @@ QHash<QString, QPair<Repository, Repository> > MetadataJob::searchAdditionalRepo
}
} else {
qDebug() << "Invalid additional repositories action set in Updates.xml fetched "
- "from" << metadata.repository.displayname() << "line:" << el.lineNumber();
+ "from" << metadata.repository().displayname() << "line:" << el.lineNumber();
}
}
}
@@ -842,7 +993,7 @@ MetadataJob::Status MetadataJob::setAdditionalRepositories(QHash<QString, QPair<
Settings &s = m_core->settings();
const QSet<Repository> temporaries = s.temporaryRepositories();
// in case the temp repository introduced something new, we only want that temporary
- if (temporaries.contains(metadata.repository)) {
+ if (temporaries.contains(metadata.repository())) {
QSet<Repository> tmpRepositories;
typedef QPair<Repository, Repository> RepositoryPair;
@@ -858,7 +1009,9 @@ MetadataJob::Status MetadataJob::setAdditionalRepositories(QHash<QString, QPair<
if (tmpRepositories.count() > 0) {
s.addTemporaryRepositories(tmpRepositories, true);
QFile::remove(result.target());
- m_metaFromDefaultRepositories.clear();
+ m_defaultRepositoriesFetched = false;
+ qDeleteAll(m_fetchedMetadata);
+ m_fetchedMetadata.clear();
status = XmlDownloadRetry;
}
} else if (s.updateDefaultRepositories(repositoryUpdates) == Settings::UpdatesApplied) {
@@ -872,7 +1025,9 @@ MetadataJob::Status MetadataJob::setAdditionalRepositories(QHash<QString, QPair<
if (gainedAdminRights)
m_core->dropAdminRights();
}
- m_metaFromDefaultRepositories.clear();
+ m_defaultRepositoriesFetched = false;
+ qDeleteAll(m_fetchedMetadata);
+ m_fetchedMetadata.clear();
QFile::remove(result.target());
status = XmlDownloadRetry;
}
diff --git a/src/libs/installer/metadatajob.h b/src/libs/installer/metadatajob.h
index d3b404c58..c0412b9cd 100644
--- a/src/libs/installer/metadatajob.h
+++ b/src/libs/installer/metadatajob.h
@@ -32,6 +32,8 @@
#include "downloadfiletask.h"
#include "fileutils.h"
#include "job.h"
+#include "metadata.h"
+#include "genericdatacache.h"
#include "repository.h"
#include <QFutureWatcher>
@@ -43,18 +45,6 @@ namespace QInstaller {
class PackageManagerCore;
-struct Metadata
-{
- QString directory;
- Repository repository;
-};
-
-struct ArchiveMetadata
-{
- QString archive;
- Metadata metaData;
-};
-
enum DownloadType
{
All,
@@ -77,12 +67,15 @@ public:
explicit MetadataJob(QObject *parent = 0);
~MetadataJob();
- QList<Metadata> metadata() const;
- Repository repositoryForDirectory(const QString &directory) const;
+ QList<Metadata *> metadata() const;
+ Repository repositoryForCacheDirectory(const QString &directory) const;
void setPackageManagerCore(PackageManagerCore *core) { m_core = core; }
void addDownloadType(DownloadType downloadType) { m_downloadType = downloadType;}
QStringList shaMismatchPackages() const { return m_shaMissmatchPackages; }
+ bool resetCache(bool init = false);
+ bool clearCache();
+
private slots:
void doStart() override;
void doCancel() override;
@@ -98,13 +91,16 @@ private slots:
private:
bool fetchMetaDataPackages();
void startUnzipRepositoryTask(const Repository &repo);
+ bool updateCache();
+ void resetCacheRepositories();
void reset();
void resetCompressedFetch();
Status parseUpdatesXml(const QList<FileTaskResult> &results);
+ Status parseRepositoryUpdates(const QDomElement &root, const FileTaskResult &result, Metadata *metadata);
QSet<Repository> getRepositories();
- void addFileTaskItem(const QString &source, const QString &target, const Metadata &metadata,
+ void addFileTaskItem(const QString &source, const QString &target, Metadata *metadata,
const QString &sha1, const QString &packageName);
- bool parsePackageUpdate(const QDomNodeList &c2, QString &packageName, QString &packageVersion,
+ static bool parsePackageUpdate(const QDomNodeList &c2, QString &packageName, QString &packageVersion,
QString &packageHash, bool online, bool testCheckSum);
QHash<QString, QPair<Repository, Repository> > searchAdditionalRepositories(const QDomNode &repositoryUpdate,
const FileTaskResult &result, const Metadata &metadata);
@@ -112,10 +108,13 @@ private:
const FileTaskResult &result, const Metadata& metadata);
private:
+ friend class Metadata;
+
+private:
PackageManagerCore *m_core;
QList<FileTaskItem> m_packages;
- TempDirDeleter m_tempDirDeleter;
+ TempPathDeleter m_tempDirDeleter;
QFutureWatcher<FileTaskResult> m_xmlTask;
QFutureWatcher<FileTaskResult> m_metadataTask;
QHash<QFutureWatcher<void> *, QObject*> m_unzipTasks;
@@ -127,9 +126,11 @@ private:
int m_taskNumber;
int m_totalTaskCount;
QStringList m_shaMissmatchPackages;
- QMultiHash<QString, ArchiveMetadata> m_fetchedArchive;
- QHash<QString, Metadata> m_metaFromDefaultRepositories;
- QHash<QString, Metadata> m_metaFromArchive; //for faster lookups.
+ bool m_defaultRepositoriesFetched;
+
+ QSet<Repository> m_fetchedCategorizedRepositories;
+ QHash<QString, Metadata *> m_fetchedMetadata;
+ GenericDataCache<Metadata> m_metaFromCache;
};
} // namespace QInstaller
diff --git a/src/libs/installer/metadatajob_p.h b/src/libs/installer/metadatajob_p.h
index 5fd44e6f9..2456a971e 100644
--- a/src/libs/installer/metadatajob_p.h
+++ b/src/libs/installer/metadatajob_p.h
@@ -32,6 +32,7 @@
#include "archivefactory.h"
#include "metadatajob.h"
+#include <QCryptographicHash>
#include <QDir>
#include <QFile>
@@ -86,6 +87,9 @@ public:
fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting "
"archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString())));
}
+ // Don't need the archive anymore
+ archive->close();
+ QFile::remove(m_archive);
fi.reportFinished();
}
diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp
index c68f93efe..2abfab2f6 100644
--- a/src/libs/installer/packagemanagercore.cpp
+++ b/src/libs/installer/packagemanagercore.cpp
@@ -574,6 +574,16 @@ void PackageManagerCore::cancelMetaInfoJob()
}
/*!
+ Clears the contents of the cache used to store downloaded metadata.
+
+ \sa {installer::clearLocalCache}{installer.clearLocalCache}
+*/
+void PackageManagerCore::clearLocalCache()
+{
+ d->m_metadataJob.clearCache();
+}
+
+/*!
\sa {installer::componentsToInstallNeedsRecalculation}{installer.componentsToInstallNeedsRecalculation}
*/
void PackageManagerCore::componentsToInstallNeedsRecalculation()
@@ -844,6 +854,11 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize)
connect(&archivesJob, &DownloadArchivesJob::downloadStatusChanged,
ProgressCoordinator::instance(), &ProgressCoordinator::additionalProgressStatusChanged);
+ connect(&archivesJob, &DownloadArchivesJob::fileDownloadReady,
+ d, &PackageManagerCorePrivate::addPathForDeletion);
+ connect(&archivesJob, &DownloadArchivesJob::hashDownloadReady,
+ d, &PackageManagerCorePrivate::addPathForDeletion);
+
ProgressCoordinator::instance()->registerPartProgress(&archivesJob,
SIGNAL(progressChanged(double)), partProgressSize);
@@ -3946,7 +3961,7 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo
}
- const Repository repo = d->m_metadataJob.repositoryForDirectory(localPath);
+ const Repository repo = d->m_metadataJob.repositoryForCacheDirectory(localPath);
if (repo.isValid()) {
component->setRepositoryUrl(repo.url());
component->setValue(QLatin1String("username"), repo.username());
diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h
index ce8e6a9aa..4ee97a86f 100644
--- a/src/libs/installer/packagemanagercore.h
+++ b/src/libs/installer/packagemanagercore.h
@@ -354,6 +354,7 @@ public Q_SLOTS:
void languageChanged();
void setCompleteUninstallation(bool complete);
void cancelMetaInfoJob();
+ void clearLocalCache();
void componentsToInstallNeedsRecalculation();
void calculateUserSelectedComponentsToInstall(const QList<QModelIndex> &indexes);
void clearComponentsToInstallCalculated();
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp
index f4d47b718..5350a5e37 100644
--- a/src/libs/installer/packagemanagercore_p.cpp
+++ b/src/libs/installer/packagemanagercore_p.cpp
@@ -662,6 +662,8 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
m_metadataJob.disconnect();
m_metadataJob.setAutoDelete(false);
m_metadataJob.setPackageManagerCore(m_core);
+ m_metadataJob.resetCache(true);
+
connect(&m_metadataJob, &Job::infoMessage, this, &PackageManagerCorePrivate::infoMessage);
connect(&m_metadataJob, &Job::progress, this, &PackageManagerCorePrivate::infoProgress);
connect(&m_metadataJob, &Job::totalProgress, this, &PackageManagerCorePrivate::totalProgress);
@@ -2800,7 +2802,7 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe
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;
@@ -2816,18 +2818,18 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe
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");
+ const QString updatesXmlPath = data->path() + QLatin1String("/Updates.xml");
QFile updatesFile(updatesXmlPath);
try {
QInstaller::openForRead(&updatesFile);
@@ -2854,12 +2856,12 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe
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));
else
- m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 0));
+ m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data->path()), 0));
- 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)) {
@@ -2961,6 +2963,15 @@ 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)
{
diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h
index 60b11ebda..d7c0b30d3 100644
--- a/src/libs/installer/packagemanagercore_p.h
+++ b/src/libs/installer/packagemanagercore_p.h
@@ -36,6 +36,7 @@
#include "packagesource.h"
#include "qinstallerglobal.h"
#include "component.h"
+#include "fileutils.h"
#include "sysinfo.h"
#include "updatefinder.h"
@@ -55,7 +56,6 @@ namespace QInstaller {
struct BinaryLayout;
class ScriptEngine;
class ComponentModel;
-class TempDirDeleter;
class InstallerCalculator;
class UninstallerCalculator;
class RemoteFileEngineHandler;
@@ -240,6 +240,7 @@ private slots:
}
void handleMethodInvocationRequest(const QString &invokableMethodName);
+ void addPathForDeletion(const QString &path);
private:
void unpackAndInstallComponents(const QList<Component *> &components,
@@ -282,6 +283,7 @@ private:
private:
PackageManagerCore *m_core;
MetadataJob m_metadataJob;
+ TempPathDeleter m_tmpPathDeleter;
bool m_updates;
bool m_repoFetched;
diff --git a/src/libs/installer/qinstallerglobal.h b/src/libs/installer/qinstallerglobal.h
index 88f255af6..a750e6583 100644
--- a/src/libs/installer/qinstallerglobal.h
+++ b/src/libs/installer/qinstallerglobal.h
@@ -46,7 +46,8 @@ enum INSTALLER_EXPORT JobError
InvalidMetaInfo,
ExtractionError,
UserIgnoreError,
- RepositoryUpdatesReceived
+ RepositoryUpdatesReceived,
+ CacheError
};
typedef KDUpdater::UpdateOperation Operation;
diff --git a/src/libs/installer/repository.cpp b/src/libs/installer/repository.cpp
index 50f3eceb6..ddbf3b382 100644
--- a/src/libs/installer/repository.cpp
+++ b/src/libs/installer/repository.cpp
@@ -50,7 +50,6 @@ Repository::Repository()
, m_enabled(false)
, m_compressed(false)
{
- registerMetaType();
}
/*!
@@ -66,7 +65,6 @@ Repository::Repository(const Repository &other)
, m_compressed(other.m_compressed)
, m_categoryname(other.m_categoryname)
{
- registerMetaType();
}
/*!
@@ -79,7 +77,6 @@ Repository::Repository(const QUrl &url, bool isDefault, bool compressed)
, m_enabled(true)
, m_compressed(compressed)
{
- registerMetaType();
}
/*!
diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp
index 108d68832..df7d5f8cc 100644
--- a/src/libs/installer/settings.cpp
+++ b/src/libs/installer/settings.cpp
@@ -34,8 +34,11 @@
#include "globals.h"
#include "fileutils.h"
+#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QStringList>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QUuid>
#include <QtGui/QFontMetrics>
#include <QtWidgets/QApplication>
@@ -76,6 +79,8 @@ static const QLatin1String scFtpProxy("FtpProxy");
static const QLatin1String scHttpProxy("HttpProxy");
static const QLatin1String scProxyType("ProxyType");
+static const QLatin1String scLocalCachePath("LocalCachePath");
+
const char scControlScript[] = "ControlScript";
template <typename T>
@@ -315,7 +320,7 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
<< scInstallerApplicationIcon << scInstallerWindowIcon
<< scLogo << scWatermark << scBanner << scBackground << scPageListPixmap
<< scStartMenuDir << scMaintenanceToolName << scMaintenanceToolIniFile << scMaintenanceToolAlias
- << scRemoveTargetDir
+ << scRemoveTargetDir << scLocalCacheDir << scPersistentLocalCache
<< scRunProgram << scRunProgramArguments << scRunProgramDescription
<< scDependsOnLocalInstallerBinary
<< scAllowSpaceInPath << scAllowNonAsciiCharacters << scDisableAuthorizationFallback
@@ -872,6 +877,40 @@ void Settings::setRepositorySettingsPageVisible(bool visible)
d->m_data.replace(scRepositorySettingsPageVisible, visible);
}
+bool Settings::persistentLocalCache() const
+{
+ return d->m_data.value(scPersistentLocalCache, true).toBool();
+}
+
+void Settings::setPersistentLocalCache(bool enable)
+{
+ d->m_data.replace(scPersistentLocalCache, enable);
+}
+
+QString Settings::localCacheDir() const
+{
+ const QString fallback = QLatin1String("qt-installer-framework") + QDir::separator()
+ + QUuid::createUuidV3(QUuid(), applicationName()).toString(QUuid::WithoutBraces);
+ return d->m_data.value(scLocalCacheDir, fallback).toString();
+}
+
+void Settings::setLocalCacheDir(const QString &dir)
+{
+ d->m_data.replace(scLocalCacheDir, dir);
+}
+
+QString Settings::localCachePath() const
+{
+ const QString fallback = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)
+ + QDir::separator() + localCacheDir();
+ return d->m_data.value(scLocalCachePath, fallback).toString();
+}
+
+void Settings::setLocalCachePath(const QString &path)
+{
+ d->m_data.replace(scLocalCachePath, path);
+}
+
Settings::ProxyType Settings::proxyType() const
{
return Settings::ProxyType(d->m_data.value(scProxyType, Settings::NoProxy).toInt());
diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h
index c34ae2ee9..ebb65a382 100644
--- a/src/libs/installer/settings.h
+++ b/src/libs/installer/settings.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -153,6 +153,15 @@ public:
bool repositorySettingsPageVisible() const;
void setRepositorySettingsPageVisible(bool visible);
+ bool persistentLocalCache() const;
+ void setPersistentLocalCache(bool enable);
+
+ QString localCacheDir() const;
+ void setLocalCacheDir(const QString &dir);
+
+ QString localCachePath() const;
+ void setLocalCachePath(const QString &path);
+
Settings::ProxyType proxyType() const;
void setProxyType(Settings::ProxyType type);
diff --git a/src/sdk/settingsdialog.cpp b/src/sdk/settingsdialog.cpp
index c3159ac36..b807deccd 100644
--- a/src/sdk/settingsdialog.cpp
+++ b/src/sdk/settingsdialog.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -194,6 +194,7 @@ SettingsDialog::SettingsDialog(PackageManagerCore *core, QWidget *parent)
, m_ui(new Ui::SettingsDialog)
, m_core(core)
, m_showPasswords(false)
+ , m_cacheCleared(false)
{
m_ui->setupUi(this);
setupRepositoriesTreeWidget();
@@ -238,6 +239,12 @@ SettingsDialog::SettingsDialog(PackageManagerCore *core, QWidget *parent)
this, &SettingsDialog::selectAll);
connect(m_ui->m_deselectAll, &QAbstractButton::clicked,
this, &SettingsDialog::deselectAll);
+
+ connect(m_ui->m_clearPushButton, &QAbstractButton::clicked,
+ this, &SettingsDialog::clearLocalCacheClicked);
+ connect(m_ui->m_clearPushButton, &QAbstractButton::clicked,
+ this, [&] { m_cacheCleared = true; });
+
useTmpRepositoriesOnly(settings.hasReplacementRepos());
m_ui->m_useTmpRepositories->setChecked(settings.hasReplacementRepos());
m_ui->m_useTmpRepositories->setEnabled(settings.hasReplacementRepos());
@@ -248,6 +255,8 @@ SettingsDialog::SettingsDialog(PackageManagerCore *core, QWidget *parent)
m_ui->m_repositories->setParent(this);
m_ui->m_repositories->setVisible(settings.repositorySettingsPageVisible());
}
+
+ m_ui->m_cachePathLineEdit->setText(settings.localCachePath());
}
void SettingsDialog::accept()
@@ -290,6 +299,14 @@ void SettingsDialog::accept()
settingsChanged |= (settings.httpProxy() != newSettings.httpProxy());
}
+ // need to fetch metadata again
+ settingsChanged |= m_cacheCleared;
+ m_cacheCleared = false;
+
+ // update cache path
+ newSettings.setLocalCachePath(m_ui->m_cachePathLineEdit->text());
+ settingsChanged |= (settings.localCachePath() != newSettings.localCachePath());
+
if (settingsChanged)
emit networkSettingsChanged(newSettings);
diff --git a/src/sdk/settingsdialog.h b/src/sdk/settingsdialog.h
index 97bdd0467..bc618d6b7 100644
--- a/src/sdk/settingsdialog.h
+++ b/src/sdk/settingsdialog.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -109,6 +109,7 @@ public slots:
signals:
void networkSettingsChanged(const QInstaller::Settings &settings);
+ void clearLocalCacheClicked();
private slots:
void addRepository();
@@ -131,6 +132,7 @@ private:
QInstaller::PackageManagerCore *m_core;
bool m_showPasswords;
+ bool m_cacheCleared;
QList<QTreeWidgetItem*> m_rootItems;
};
diff --git a/src/sdk/settingsdialog.ui b/src/sdk/settingsdialog.ui
index 6645e6460..0254abd98 100644
--- a/src/sdk/settingsdialog.ui
+++ b/src/sdk/settingsdialog.ui
@@ -274,6 +274,66 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="m_localCache">
+ <attribute name="title">
+ <string>Local cache</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QLabel" name="m_cacheDescriptionLabel">
+ <property name="text">
+ <string>The meta information from remote repositories is cached to disk to improve loading times. You may select another directory to store the cache or clear the contents of the current cache.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="m_cachePathLabel">
+ <property name="text">
+ <string>Path for cache:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="m_cachePathLineEdit"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="m_clearPushButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Deletes the contents of the cache directory</string>
+ </property>
+ <property name="text">
+ <string>Clear cache</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
<item>
diff --git a/src/sdk/tabcontroller.cpp b/src/sdk/tabcontroller.cpp
index 56ab524aa..3cb3e4089 100644
--- a/src/sdk/tabcontroller.cpp
+++ b/src/sdk/tabcontroller.cpp
@@ -154,6 +154,8 @@ void TabController::restartWizard()
d->m_core->settings().setDefaultRepositories(d->m_settings.defaultRepositories());
d->m_core->settings().setTemporaryRepositories(d->m_settings.temporaryRepositories(),
d->m_settings.hasReplacementRepos());
+ d->m_core->settings().setLocalCachePath(d->m_settings.localCachePath());
+
d->m_core->networkSettingsChanged();
}
@@ -171,6 +173,8 @@ void TabController::onSettingsButtonClicked()
SettingsDialog dialog(d->m_core);
connect(&dialog, &SettingsDialog::networkSettingsChanged,
this, &TabController::onNetworkSettingsChanged);
+ connect(&dialog, &SettingsDialog::clearLocalCacheClicked,
+ this, [&] { d->m_core->clearLocalCache(); });
dialog.exec();
if (d->m_networkSettingsChanged) {
diff --git a/tests/auto/installer/appendfileoperation/data/repository/Updates.xml b/tests/auto/installer/appendfileoperation/data/repository/Updates.xml
index a1c8f6aa2..f12d387a1 100644
--- a/tests/auto/installer/appendfileoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/appendfileoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,6 +10,7 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>750eda14d867849aeb2f47d620f6e5f32134f375</SHA1>
<DownloadableArchives>content.7z</DownloadableArchives>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/commandlineinstall/data/filequeryrepository/Updates.xml b/tests/auto/installer/commandlineinstall/data/filequeryrepository/Updates.xml
index 72b7938d9..7957cde4e 100644
--- a/tests/auto/installer/commandlineinstall/data/filequeryrepository/Updates.xml
+++ b/tests/auto/installer/commandlineinstall/data/filequeryrepository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>4b124046df83fcd12fb7126b795a8b5a62602806</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/commandlineinstall/tst_commandlineinstall.cpp b/tests/auto/installer/commandlineinstall/tst_commandlineinstall.cpp
index 89f6153ed..1e269b7ff 100644
--- a/tests/auto/installer/commandlineinstall/tst_commandlineinstall.cpp
+++ b/tests/auto/installer/commandlineinstall/tst_commandlineinstall.cpp
@@ -133,30 +133,30 @@ private slots:
QLoggingCategory::setFilterRules(loggingRules);
- QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
+ QTest::ignoreMessage(QtDebugMsg, "Fetching latest update information...");
QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Cannot install component A. Component "
"is installed only as automatic dependency to autoDep.\n"));
QCOMPARE(PackageManagerCore::Canceled, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("A")));
- QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
+ QTest::ignoreMessage(QtDebugMsg, "Fetching latest update information...");
QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Cannot install component AB. Component "
"is not checkable, meaning you have to select one of the subcomponents.\n"));
QCOMPARE(PackageManagerCore::Canceled, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("AB")));
- QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
+ QTest::ignoreMessage(QtDebugMsg, "Fetching latest update information...");
QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Cannot install B. Component is virtual.\n"));
QCOMPARE(PackageManagerCore::Canceled, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("B")));
- QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
+ QTest::ignoreMessage(QtDebugMsg, "Fetching latest update information...");
QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Cannot install B.subcomponent. Component "
"is a descendant of a virtual component B.\n"));
QCOMPARE(PackageManagerCore::Canceled, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("B.subcomponent")));
- QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
+ QTest::ignoreMessage(QtDebugMsg, "Fetching latest update information...");
QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Cannot install MissingComponent. "
"Component not found.\n"));
QCOMPARE(PackageManagerCore::Canceled, core->installSelectedComponentsSilently(QStringList()
diff --git a/tests/auto/installer/consumeoutputoperationtest/data/repository/Updates.xml b/tests/auto/installer/consumeoutputoperationtest/data/repository/Updates.xml
index 6b1856d51..1c3caf7be 100644
--- a/tests/auto/installer/consumeoutputoperationtest/data/repository/Updates.xml
+++ b/tests/auto/installer/consumeoutputoperationtest/data/repository/Updates.xml
@@ -10,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>4da14562d6515590d145678d21990faa817832bb</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/copydirectoryoperation/data/repository/Updates.xml b/tests/auto/installer/copydirectoryoperation/data/repository/Updates.xml
index a1c8f6aa2..fb8c9cf1c 100644
--- a/tests/auto/installer/copydirectoryoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/copydirectoryoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,6 +10,7 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>4fd4dd023111fcbbdd1221032c2984d249f4cad1</SHA1>
<DownloadableArchives>content.7z</DownloadableArchives>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/copydirectoryoperation/data/xmloperationrepository/Updates.xml b/tests/auto/installer/copydirectoryoperation/data/xmloperationrepository/Updates.xml
index 90b26a387..b495f3307 100644
--- a/tests/auto/installer/copydirectoryoperation/data/xmloperationrepository/Updates.xml
+++ b/tests/auto/installer/copydirectoryoperation/data/xmloperationrepository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -8,6 +9,7 @@
<Version>1.0.2-1</Version>
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
+ <SHA1>4fd4dd023111fcbbdd1221032c2984d249f4cad1</SHA1>
<DownloadableArchives>content.7z</DownloadableArchives>
<Operations>
<Operation name="Mkdir">
diff --git a/tests/auto/installer/createdesktopentryoperation/data/repository/Updates.xml b/tests/auto/installer/createdesktopentryoperation/data/repository/Updates.xml
index 77b5a9956..ad6c49c81 100644
--- a/tests/auto/installer/createdesktopentryoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/createdesktopentryoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>0746c8292e3799aac4a534a0a1a58d42ef24ed43</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/createshortcutoperation/data/repository/Updates.xml b/tests/auto/installer/createshortcutoperation/data/repository/Updates.xml
index 0826afae8..d1909d355 100644
--- a/tests/auto/installer/createshortcutoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/createshortcutoperation/data/repository/Updates.xml
@@ -5,7 +5,7 @@
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
- <Description>Example component A</Description>
+ <Description>Example component for CreateShortcutOperation</Description>
<Version>1.0.2-1</Version>
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
diff --git a/tests/auto/installer/deleteoperation/data/repository/Updates.xml b/tests/auto/installer/deleteoperation/data/repository/Updates.xml
index 77b5a9956..5a8b28f38 100644
--- a/tests/auto/installer/deleteoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/deleteoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>e8b8ce98862d463c855609ed8e139eda17092cc6</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/environmentvariableoperation/data/repository/Updates.xml b/tests/auto/installer/environmentvariableoperation/data/repository/Updates.xml
index 6b1856d51..5bcd58c69 100644
--- a/tests/auto/installer/environmentvariableoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/environmentvariableoperation/data/repository/Updates.xml
@@ -10,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>4b7b52af2d838389a7404c553da74705fc106493</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/installiconsoperation/data/repository/Updates.xml b/tests/auto/installer/installiconsoperation/data/repository/Updates.xml
index a1c8f6aa2..201e23c2d 100644
--- a/tests/auto/installer/installiconsoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/installiconsoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -10,5 +11,6 @@
<Default>true</Default>
<Script>script.qs</Script>
<DownloadableArchives>content.7z</DownloadableArchives>
+ <SHA1>13c9e7e67c26e7fbf49cc30887d87140b65e11b5</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/licenseagreement/data/repository/Updates.xml b/tests/auto/installer/licenseagreement/data/repository/Updates.xml
index 2afd2a741..c57918162 100644
--- a/tests/auto/installer/licenseagreement/data/repository/Updates.xml
+++ b/tests/auto/installer/licenseagreement/data/repository/Updates.xml
@@ -9,6 +9,7 @@
<Version>1.0.2-1</Version>
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
+ <SHA1>f46c677db8bc779d70d0c72fae264a321caea6f8</SHA1>
<Licenses>
<License name="GNU GENERAL PUBLIC LICENSE Version 3" file="gpl3.txt"/>
</Licenses>
diff --git a/tests/auto/installer/messageboxhandler/data/invalidoperation/Updates.xml b/tests/auto/installer/messageboxhandler/data/invalidoperation/Updates.xml
index 77b5a9956..f92211497 100644
--- a/tests/auto/installer/messageboxhandler/data/invalidoperation/Updates.xml
+++ b/tests/auto/installer/messageboxhandler/data/invalidoperation/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>c8eb49045188859100716ab084452b32fca38d5d</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/messageboxhandler/data/messagebox/Updates.xml b/tests/auto/installer/messageboxhandler/data/messagebox/Updates.xml
index 77b5a9956..0781dcfc5 100644
--- a/tests/auto/installer/messageboxhandler/data/messagebox/Updates.xml
+++ b/tests/auto/installer/messageboxhandler/data/messagebox/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>ad3157ed059e3369c094e154319de1d255865de5</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/moveoperation/data/repository/Updates.xml b/tests/auto/installer/moveoperation/data/repository/Updates.xml
index 6b1856d51..5cdad4e45 100644
--- a/tests/auto/installer/moveoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/moveoperation/data/repository/Updates.xml
@@ -10,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>badc75810d399a35bae6f6b2cd8acfc1d5b1ccd2</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/registerfiletypeoperation/data/repository/Updates.xml b/tests/auto/installer/registerfiletypeoperation/data/repository/Updates.xml
index 0826afae8..6435ae0f7 100644
--- a/tests/auto/installer/registerfiletypeoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/registerfiletypeoperation/data/repository/Updates.xml
@@ -5,7 +5,7 @@
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
- <Description>Example component A</Description>
+ <Description>Example component for RegisterFileTypeOperation</Description>
<Version>1.0.2-1</Version>
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
diff --git a/tests/auto/installer/replaceoperation/data/repository/Updates.xml b/tests/auto/installer/replaceoperation/data/repository/Updates.xml
index a0ade298c..7585b57a4 100644
--- a/tests/auto/installer/replaceoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/replaceoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,6 +10,7 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>edb7672270729f9d34396cb70e6afd12fec90d2b</SHA1>
<UpdateFile CompressedSize="225" UncompressedSize="75" OS="Any"/>
<DownloadableArchives>content.7z</DownloadableArchives>
</PackageUpdate>
diff --git a/tests/auto/installer/repository/data/compressedRepository/compressedRepository.7z b/tests/auto/installer/repository/data/compressedRepository/compressedRepository.7z
index fa75f2979..335685bb0 100644
--- a/tests/auto/installer/repository/data/compressedRepository/compressedRepository.7z
+++ b/tests/auto/installer/repository/data/compressedRepository/compressedRepository.7z
Binary files differ
diff --git a/tests/auto/installer/repository/data/repository/Updates.xml b/tests/auto/installer/repository/data/repository/Updates.xml
index 6fdfec9e5..8ddea3938 100644
--- a/tests/auto/installer/repository/data/repository/Updates.xml
+++ b/tests/auto/installer/repository/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
diff --git a/tests/auto/installer/settingsoperation/data/repository/Updates.xml b/tests/auto/installer/settingsoperation/data/repository/Updates.xml
index 77b5a9956..eddb160c0 100644
--- a/tests/auto/installer/settingsoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/settingsoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>5dc8a98f591998de0c555e194e228fa740a15632</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/installer/simplemovefileoperation/data/repository/Updates.xml b/tests/auto/installer/simplemovefileoperation/data/repository/Updates.xml
index 77b5a9956..6df6a436b 100644
--- a/tests/auto/installer/simplemovefileoperation/data/repository/Updates.xml
+++ b/tests/auto/installer/simplemovefileoperation/data/repository/Updates.xml
@@ -1,6 +1,7 @@
<Updates>
<ApplicationName>{AnyApplication}</ApplicationName>
<ApplicationVersion>1.0.0</ApplicationVersion>
+ <Checksum>false</Checksum>
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
@@ -9,5 +10,6 @@
<ReleaseDate>2015-01-01</ReleaseDate>
<Default>true</Default>
<Script>script.qs</Script>
+ <SHA1>8f2df84c9eada2570c02a7df573288e5cb6644e2</SHA1>
</PackageUpdate>
</Updates>
diff --git a/tests/auto/tools/repotest/test_package_versions/repository_1/Updates.xml b/tests/auto/tools/repotest/test_package_versions/repository_1/Updates.xml
index dfae8c8bd..edb6515bd 100644
--- a/tests/auto/tools/repotest/test_package_versions/repository_1/Updates.xml
+++ b/tests/auto/tools/repotest/test_package_versions/repository_1/Updates.xml
@@ -5,7 +5,7 @@
<PackageUpdate>
<Name>A</Name>
<DisplayName>A</DisplayName>
- <Description>Example component A</Description>
+ <Description>Test component A</Description>
<Version>2.0.0</Version>
<ReleaseDate>2020-01-01</ReleaseDate>
<Default>true</Default>
@@ -17,7 +17,7 @@
<PackageUpdate>
<Name>B</Name>
<DisplayName>B</DisplayName>
- <Description>Example component B</Description>
+ <Description>Test component B</Description>
<Version>1.0.0</Version>
<ReleaseDate>2020-01-01</ReleaseDate>
<Default>true</Default>
diff --git a/tests/auto/tools/repotest/tst_repotest.cpp b/tests/auto/tools/repotest/tst_repotest.cpp
index af48ba3fc..f1c0ae9dd 100644
--- a/tests/auto/tools/repotest/tst_repotest.cpp
+++ b/tests/auto/tools/repotest/tst_repotest.cpp
@@ -574,7 +574,7 @@ private slots:
private:
QInstallerTools::RepositoryInfo m_repoInfo;
QInstallerTools::PackageInfoVector m_packages;
- TempDirDeleter m_tempDirDeleter;
+ TempPathDeleter m_tempDirDeleter;
};
QTEST_MAIN(tst_repotest)