summaryrefslogtreecommitdiff
path: root/src/common-lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/common-lib')
-rw-r--r--src/common-lib/common-lib.pro5
-rw-r--r--src/common-lib/configcache.cpp414
-rw-r--r--src/common-lib/configcache.h158
-rw-r--r--src/common-lib/configcache_p.h84
-rw-r--r--src/common-lib/crashhandler.cpp2
-rw-r--r--src/common-lib/crashhandler.h2
-rw-r--r--src/common-lib/dbus-utilities.cpp2
-rw-r--r--src/common-lib/dbus-utilities.h2
-rw-r--r--src/common-lib/error.h2
-rw-r--r--src/common-lib/exception.cpp2
-rw-r--r--src/common-lib/exception.h24
-rw-r--r--src/common-lib/global.h2
-rw-r--r--src/common-lib/logging.cpp23
-rw-r--r--src/common-lib/logging.h5
-rw-r--r--src/common-lib/processtitle.cpp2
-rw-r--r--src/common-lib/processtitle.h2
-rw-r--r--src/common-lib/qml-utilities.cpp2
-rw-r--r--src/common-lib/qml-utilities.h2
-rw-r--r--src/common-lib/qtyaml.cpp746
-rw-r--r--src/common-lib/qtyaml.h84
-rw-r--r--src/common-lib/startuptimer.cpp27
-rw-r--r--src/common-lib/startuptimer.h2
-rw-r--r--src/common-lib/unixsignalhandler.cpp2
-rw-r--r--src/common-lib/unixsignalhandler.h2
-rw-r--r--src/common-lib/utilities.cpp65
-rw-r--r--src/common-lib/utilities.h45
26 files changed, 1400 insertions, 308 deletions
diff --git a/src/common-lib/common-lib.pro b/src/common-lib/common-lib.pro
index f3f99251..4d0e3efa 100644
--- a/src/common-lib/common-lib.pro
+++ b/src/common-lib/common-lib.pro
@@ -30,6 +30,7 @@ SOURCES += \
crashhandler.cpp \
logging.cpp \
dbus-utilities.cpp \
+ configcache.cpp
qtHaveModule(qml):SOURCES += \
qml-utilities.cpp \
@@ -44,7 +45,9 @@ HEADERS += \
unixsignalhandler.h \
processtitle.h \
crashhandler.h \
- logging.h
+ logging.h \
+ configcache.h \
+ configcache_p.h
qtHaveModule(qml):HEADERS += \
qml-utilities.h \
diff --git a/src/common-lib/configcache.cpp b/src/common-lib/configcache.cpp
new file mode 100644
index 00000000..aab695e4
--- /dev/null
+++ b/src/common-lib/configcache.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Copyright (C) 2019 Luxoft Sweden AB
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QStandardPaths>
+#include <QDataStream>
+#include <QCryptographicHash>
+#include <QElapsedTimer>
+#include <QBuffer>
+#include <QtConcurrent/QtConcurrent>
+
+#include "configcache.h"
+#include "configcache_p.h"
+#include "utilities.h"
+#include "exception.h"
+#include "logging.h"
+
+// use QtConcurrent to parse the files, if there are more than x files
+#define AM_PARALLEL_THRESHOLD 1
+
+
+QT_BEGIN_NAMESPACE_AM
+
+QDataStream &operator>>(QDataStream &ds, ConfigCacheEntry &ce)
+{
+ bool contentValid = false;
+ ds >> ce.filePath >> ce.checksum >> contentValid;
+ ce.rawContent.clear();
+ ce.content = contentValid ? reinterpret_cast<void *>(-1) : nullptr;
+ return ds;
+}
+
+QDataStream &operator<<(QDataStream &ds, const ConfigCacheEntry &ce)
+{
+ ds << ce.filePath << ce.checksum << static_cast<bool>(ce.content);
+ return ds;
+}
+
+QDataStream &operator>>(QDataStream &ds, CacheHeader &ch)
+{
+ ds >> ch.magic >> ch.version >> ch.globalId >> ch.baseName >> ch.entries;
+ return ds;
+}
+
+QDataStream &operator<<(QDataStream &ds, const CacheHeader &ch)
+{
+ ds << ch.magic << ch.version << ch.globalId << ch.baseName << ch.entries;
+ return ds;
+}
+
+QDebug operator<<(QDebug dbg, const ConfigCacheEntry &ce)
+{
+ dbg << "CacheEntry {\n " << ce.filePath << "\n " << ce.checksum.toHex() << "\n valid:"
+ << (ce.content ? "yes" : "no") << ce.content
+ << "\n}\n";
+ return dbg;
+}
+
+
+// this could be used in the future to have multiple AM instances that each have their own cache
+quint64 CacheHeader::s_globalId = 0;
+
+bool CacheHeader::isValid(const QString &baseName) const
+{
+ return magic == Magic
+ && version == Version
+ && globalId == s_globalId
+ && this->baseName == baseName
+ && entries < 1000;
+}
+
+
+AbstractConfigCache::AbstractConfigCache(const QStringList &configFiles, const QString &cacheBaseName, Options options)
+ : d(new ConfigCachePrivate)
+{
+ d->options = options;
+ d->rawFiles = configFiles;
+ d->cacheBaseName = cacheBaseName;
+}
+
+AbstractConfigCache::~AbstractConfigCache()
+{
+ // make sure that clear() was called in ~Cache(), since we need the virtual destruct() function!
+ delete d;
+}
+
+void *AbstractConfigCache::takeMergedResult() const
+{
+ Q_ASSERT(d->options & MergedResult);
+ void *result = d->mergedContent;
+ d->mergedContent = nullptr;
+ return result;
+}
+
+void *AbstractConfigCache::takeResult(int index) const
+{
+ Q_ASSERT(!(d->options & MergedResult));
+ void *result = nullptr;
+ if (index >= 0 && index < d->cache.size())
+ qSwap(result, d->cache[index].content);
+ return result;
+}
+
+void *AbstractConfigCache::takeResult(const QString &rawFile) const
+{
+ return takeResult(d->cacheIndex.value(rawFile, -1));
+}
+
+void AbstractConfigCache::parse(QStringList *warnings)
+{
+ clear();
+
+ if (d->rawFiles.isEmpty())
+ return;
+
+ QElapsedTimer timer;
+ if (LogCache().isDebugEnabled())
+ timer.start();
+
+ // normalize all yaml file names
+ QStringList rawFilePaths;
+ for (const auto &rawFile : d->rawFiles)
+ rawFilePaths << QFileInfo(rawFile).canonicalFilePath();
+
+ // find the correct cache location and make sure it exists
+ const QDir cacheLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+ if (!cacheLocation.exists())
+ cacheLocation.mkpath(qSL("."));
+ const QString cacheFilePath = cacheLocation.absoluteFilePath(qSL("appman-%1.cache").arg(d->cacheBaseName));
+ QFile cacheFile(cacheFilePath);
+
+ QAtomicInt cacheIsValid = false;
+ QAtomicInt cacheIsComplete = false;
+
+ QVector<ConfigCacheEntry> cache;
+ void *mergedContent = nullptr;
+
+ qCDebug(LogCache) << d->cacheBaseName << "cache file:" << cacheFilePath;
+ qCDebug(LogCache) << d->cacheBaseName << "use-cache:" << (d->options & NoCache ? "no" : "yes")
+ << "/ clear-cache:" << (d->options & ClearCache ? "yes" : "no");
+ qCDebug(LogCache) << d->cacheBaseName << "reading:" << rawFilePaths;
+
+ if (!d->options.testFlag(NoCache) && !d->options.testFlag(ClearCache)) {
+ if (cacheFile.open(QFile::ReadOnly)) {
+ try {
+ QDataStream ds(&cacheFile);
+ CacheHeader cacheHeader;
+ ds >> cacheHeader;
+
+ if (ds.status() != QDataStream::Ok)
+ throw Exception("failed to read cache header");
+ if (!cacheHeader.isValid(d->cacheBaseName))
+ throw Exception("failed to parse cache header");
+
+ cache.resize(int(cacheHeader.entries));
+ for (int i = 0; i < int(cacheHeader.entries); ++i) {
+ ConfigCacheEntry &ce = cache[i];
+ ds >> ce;
+ if (ce.content)
+ ce.content = loadFromCache(ds);
+ }
+ if (d->options & MergedResult) {
+ mergedContent = loadFromCache(ds);
+
+ if (!mergedContent)
+ throw Exception("failed to read merged cache content");
+ }
+
+ if (ds.status() != QDataStream::Ok)
+ throw Exception("failed to read cache content");
+
+ cacheIsValid = true;
+
+ qCDebug(LogCache) << d->cacheBaseName << "loaded" << cache.size() << "entries in"
+ << timer.nsecsElapsed() / 1000 << "usec";
+
+ // check if we can use the cache as-is, or if we need to cherry-pick parts
+ if (rawFilePaths.count() == cache.count()) {
+ for (int i = 0; i < rawFilePaths.count(); ++i) {
+ const ConfigCacheEntry &ce = cache.at(i);
+ if (rawFilePaths.at(i) != ce.filePath)
+ throw Exception("the cached file names do not match the current set (or their order changed)");
+ if (!mergedContent && !ce.content)
+ throw Exception("cache entry has invalid content");
+ }
+ cacheIsComplete = true;
+ }
+
+
+ } catch (const Exception &e) {
+ if (warnings)
+ *warnings << qL1S("Failed to read cache: ") + qL1S(e.what());
+ else
+ qWarning(LogCache) << "Failed to read cache:" << e.what();
+ }
+ }
+ } else if (d->options.testFlag(ClearCache)) {
+ cacheFile.remove();
+ }
+
+ qCDebug(LogCache) << d->cacheBaseName << "valid:" << (cacheIsValid ? "yes" : "no")
+ << "/ complete:" << (cacheIsComplete ? "yes" : "no");
+
+ if (!cacheIsComplete) {
+ // we need to pick the parts we can re-use
+
+ QVector<ConfigCacheEntry> newCache(rawFilePaths.size());
+
+ // we are iterating over n^2 entries in the worst case scenario -- we could reduce it to n
+ // by using a QHash or QMap, but that doesn't come for free either: especially given the
+ // low number of processed entries (well under 100 for app manifests; around a couple for
+ // config files)
+ for (int i = 0; i < rawFilePaths.size(); ++i) {
+ const QString &rawFilePath = rawFilePaths.at(i);
+ ConfigCacheEntry &ce = newCache[i];
+
+ // if we already got this file in the cache, then use the entry
+ bool found = false;
+ for (auto it = cache.cbegin(); it != cache.cend(); ++it) {
+ if (it->filePath == rawFilePath) {
+ ce = *it;
+ found = true;
+ qCDebug(LogCache) << d->cacheBaseName << "found cache entry for" << it->filePath;
+ break;
+ }
+ }
+
+ // if it's not yet cached, then add it to the list
+ if (!found) {
+ ce.filePath = rawFilePath;
+ qCDebug(LogCache) << d->cacheBaseName << "missing cache entry for" << rawFilePath;
+ }
+ }
+ cache = newCache;
+ }
+
+ // reads a single config file and calculates its hash - defined as lambda to be usable
+ // both via QtConcurrent and via std:for_each
+ auto readConfigFile = [&cacheIsComplete, &warnings, this](ConfigCacheEntry &ce) {
+ QFile file(ce.filePath);
+ if (!file.open(QIODevice::ReadOnly))
+ throw Exception("Failed to open file '%1' for reading.\n").arg(file.fileName());
+
+ if (file.size() > 1024*1024)
+ throw Exception("File '%1' is too big (> 1MB).\n").arg(file.fileName());
+
+ ce.rawContent = file.readAll();
+ preProcessSourceContent(ce.rawContent, ce.filePath);
+
+ QByteArray checksum = QCryptographicHash::hash(ce.rawContent, QCryptographicHash::Sha1);
+ ce.checksumMatches = (checksum == ce.checksum);
+ ce.checksum = checksum;
+ if (!ce.checksumMatches) {
+ if (ce.content) {
+ if (warnings)
+ *warnings << qL1S("Failed to read Cache: cached file checksums do not match");
+ else
+ qWarning(LogCache) << "Failed to read Cache: cached file checksums do not match";
+ destruct(ce.content);
+ ce.content = nullptr;
+ }
+ cacheIsComplete = false;
+ }
+ };
+
+ // these can throw
+ if (cache.size() > AM_PARALLEL_THRESHOLD)
+ QtConcurrent::blockingMap(cache, readConfigFile);
+ else
+ std::for_each(cache.begin(), cache.end(), readConfigFile);
+
+ qCDebug(LogCache) << d->cacheBaseName << "reading all of" << cache.size() << "file(s) finished after"
+ << (timer.nsecsElapsed() / 1000) << "usec";
+ qCDebug(LogCache) << d->cacheBaseName << "still complete:" << (cacheIsComplete ? "yes" : "no");
+
+ if (!cacheIsComplete && !rawFilePaths.isEmpty()) {
+ // we have read a partial cache or none at all - parse what's not cached yet
+ QAtomicInt count;
+
+ auto parseConfigFile = [this, &count](ConfigCacheEntry &ce) {
+ if (ce.content)
+ return;
+
+ ++count;
+ try {
+ QBuffer buffer(&ce.rawContent);
+ buffer.open(QIODevice::ReadOnly);
+ ce.content = loadFromSource(&buffer, ce.filePath);
+ } catch (const Exception &e) {
+ throw Exception("Could not parse file '%1': %2")
+ .arg(ce.filePath).arg(e.errorString());
+ }
+ };
+
+ // these can throw
+ if (cache.size() > AM_PARALLEL_THRESHOLD)
+ QtConcurrent::blockingMap(cache, parseConfigFile);
+ else
+ std::for_each(cache.begin(), cache.end(), parseConfigFile);
+
+ if (d->options & MergedResult) {
+ // we cannot parallelize this step, since subsequent config files can overwrite
+ // or append to values
+ mergedContent = cache.at(0).content;
+ cache[0].content = nullptr;
+ for (int i = 1; i < cache.size(); ++i) {
+ ConfigCacheEntry &ce = cache[i];
+ merge(mergedContent, ce.content);
+ destruct(ce.content);
+ ce.content = nullptr;
+ }
+ }
+
+ qCDebug(LogCache) << d->cacheBaseName << "parsing" << count.load() << "file(s) finished after"
+ << (timer.nsecsElapsed() / 1000) << "usec";
+
+ if (!d->options.testFlag(NoCache)) {
+ // everything is parsed now, so we can write a new cache file
+
+ try {
+ QFile cacheFile(cacheFilePath);
+ if (!cacheFile.open(QFile::WriteOnly | QFile::Truncate))
+ throw Exception(cacheFile, "failed to open file for writing");
+
+ QDataStream ds(&cacheFile);
+ CacheHeader cacheHeader;
+ cacheHeader.baseName = d->cacheBaseName;
+ cacheHeader.entries = quint32(cache.size());
+ ds << cacheHeader;
+
+ for (int i = 0; i < cache.size(); ++i) {
+ const ConfigCacheEntry &ce = cache.at(i);
+ ds << ce;
+ // qCDebug(LogCache) << "SAVING" << ce << ce.content;
+ if (ce.content)
+ saveToCache(ds, ce.content);
+ }
+
+ if (d->options & MergedResult)
+ saveToCache(ds, mergedContent);
+
+ if (ds.status() != QDataStream::Ok)
+ throw Exception("error writing content");
+ } catch (const Exception &e) {
+ if (warnings)
+ *warnings << qL1S("Failed to write Cache: ") + qL1S(e.what());
+ else
+ qCWarning(LogCache) << "Failed to write Cache:" << e.what();
+ }
+ qCDebug(LogCache) << d->cacheBaseName << "writing the cache finished after"
+ << (timer.nsecsElapsed() / 1000) << "usec";
+ }
+ }
+
+ d->cache = cache;
+ if (d->options & MergedResult)
+ d->mergedContent = mergedContent;
+
+ qCDebug(LogCache) << d->cacheBaseName << "finished cache parsing after"
+ << (timer.nsecsElapsed() / 1000) << "usec";
+}
+
+void AbstractConfigCache::clear()
+{
+ for (auto &ce : qAsConst(d->cache))
+ destruct(ce.content);
+ d->cache.clear();
+ d->cacheIndex.clear();
+ destruct(d->mergedContent);
+ d->mergedContent = nullptr;
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/common-lib/configcache.h b/src/common-lib/configcache.h
new file mode 100644
index 00000000..3c15710a
--- /dev/null
+++ b/src/common-lib/configcache.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Copyright (C) 2019 Luxoft Sweden AB
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <functional>
+
+#include <QPair>
+#include <QVector>
+#include <QStringList>
+#include <QVariant>
+#include <QIODevice>
+#include <QtAppManCommon/global.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class ConfigCachePrivate;
+
+template <typename T> class ConfigCacheAdaptor
+{
+public:
+ T *loadFromSource(QIODevice *source, const QString &fileName);
+ T *loadFromCache(QDataStream &ds);
+ void saveToCache(QDataStream &ds, const T *t);
+ void merge(T *to, const T *from) { *to = *from; }
+
+ QStringList *warnings;
+};
+
+class AbstractConfigCache
+{
+public:
+ enum Option {
+ None = 0x0,
+ MergedResult = 0x1,
+ NoCache = 0x2,
+ ClearCache = 0x4
+ };
+ Q_DECLARE_FLAGS(Options, Option)
+
+ AbstractConfigCache(const QStringList &configFiles, const QString &cacheBaseName, Options options = None);
+ virtual ~AbstractConfigCache();
+
+ virtual void parse(QStringList *warnings = nullptr);
+
+ void *takeMergedResult() const;
+ void *takeResult(int index) const;
+ void *takeResult(const QString &rawFile) const;
+
+ void clear();
+
+protected:
+ virtual void *loadFromSource(QIODevice *source, const QString &fileName) = 0;
+ virtual void preProcessSourceContent(QByteArray &sourceContent, const QString &fileName) = 0;
+ virtual void *loadFromCache(QDataStream &ds) = 0;
+ virtual void saveToCache(QDataStream &ds, const void *t) = 0;
+ virtual void merge(void *to, const void *from) = 0;
+ virtual void destruct(void *t) = 0;
+
+private:
+ Q_DISABLE_COPY(AbstractConfigCache)
+
+ ConfigCachePrivate *d;
+};
+
+template <typename T, typename ADAPTOR = ConfigCacheAdaptor<T>> class ConfigCache : public AbstractConfigCache
+{
+public:
+ using AbstractConfigCache::Option;
+ using AbstractConfigCache::Options;
+
+ ConfigCache(const QStringList &configFiles, const QString &cacheBaseName, Options options = None)
+ : AbstractConfigCache(configFiles, cacheBaseName, options)
+ { }
+
+ ~ConfigCache()
+ {
+ clear();
+ }
+
+ void parse(QStringList *warnings = nullptr) override
+ {
+ m_adaptor.warnings = warnings;
+ AbstractConfigCache::parse(warnings);
+ }
+
+ T *takeMergedResult() const
+ {
+ return static_cast<T *>(AbstractConfigCache::takeMergedResult());
+ }
+ T *takeResult(int index) const
+ {
+ return static_cast<T *>(AbstractConfigCache::takeResult(index));
+ }
+ T *takeResult(const QString &yamlFile) const
+ {
+ return static_cast<T *>(AbstractConfigCache::takeResult(yamlFile));
+ }
+
+protected:
+ void *loadFromSource(QIODevice *source, const QString &fileName) override
+ { return m_adaptor.loadFromSource(source, fileName); }
+ void preProcessSourceContent(QByteArray &sourceContent, const QString &fileName) override
+ { m_adaptor.preProcessSourceContent(sourceContent, fileName); }
+ void *loadFromCache(QDataStream &ds) override
+ { return m_adaptor.loadFromCache(ds); }
+ void saveToCache(QDataStream &ds, const void *t) override
+ { m_adaptor.saveToCache(ds, static_cast<const T *>(t)); }
+ void merge(void *to, const void *from) override
+ { m_adaptor.merge(static_cast<T *>(to), static_cast<const T *>(from)); }
+ void destruct(void *t) override
+ { delete static_cast<T *>(t); }
+
+ ADAPTOR m_adaptor;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractConfigCache::Options)
+
+QT_END_NAMESPACE_AM
diff --git a/src/common-lib/configcache_p.h b/src/common-lib/configcache_p.h
new file mode 100644
index 00000000..8e690097
--- /dev/null
+++ b/src/common-lib/configcache_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Copyright (C) 2019 Luxoft Sweden AB
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include "configcache.h"
+
+QT_BEGIN_NAMESPACE_AM
+
+struct ConfigCacheEntry
+{
+ QString filePath; // abs. file path
+ QByteArray checksum; // sha1 (fast and sufficient for this use-case)
+ QByteArray rawContent; // raw YAML content
+ void *content = nullptr; // parsed YAML content
+ bool checksumMatches = false;
+};
+
+struct CacheHeader
+{
+ enum { Magic = 0x23d39366, // dd if=/dev/random bs=4 count=1 status=none | xxd -p
+ Version = 1 };
+ static quint64 s_globalId;
+
+ quint32 magic = Magic;
+ quint32 version = Version;
+ quint64 globalId = 0;
+ QString baseName;
+ quint32 entries = 0;
+
+ bool isValid(const QString &baseName) const;
+};
+
+class ConfigCachePrivate
+{
+public:
+ AbstractConfigCache::Options options;
+ QStringList rawFiles;
+ QString cacheBaseName;
+ QVector<ConfigCacheEntry> cache;
+ QMap<QString, int> cacheIndex;
+ void *mergedContent = nullptr;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/common-lib/crashhandler.cpp b/src/common-lib/crashhandler.cpp
index 480dca9a..fd196dff 100644
--- a/src/common-lib/crashhandler.cpp
+++ b/src/common-lib/crashhandler.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/crashhandler.h b/src/common-lib/crashhandler.h
index 79b42730..b09104a8 100644
--- a/src/common-lib/crashhandler.h
+++ b/src/common-lib/crashhandler.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/dbus-utilities.cpp b/src/common-lib/dbus-utilities.cpp
index ff74fb7f..1a8267b6 100644
--- a/src/common-lib/dbus-utilities.cpp
+++ b/src/common-lib/dbus-utilities.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/dbus-utilities.h b/src/common-lib/dbus-utilities.h
index c135d142..fa3de40c 100644
--- a/src/common-lib/dbus-utilities.h
+++ b/src/common-lib/dbus-utilities.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/error.h b/src/common-lib/error.h
index 1e1c16c8..6204783d 100644
--- a/src/common-lib/error.h
+++ b/src/common-lib/error.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/exception.cpp b/src/common-lib/exception.cpp
index 11a54d89..ba3e8020 100644
--- a/src/common-lib/exception.cpp
+++ b/src/common-lib/exception.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/exception.h b/src/common-lib/exception.h
index 3dcdc03a..6a238bb3 100644
--- a/src/common-lib/exception.h
+++ b/src/common-lib/exception.h
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -89,6 +90,27 @@ public:
return *this;
}
+ // this will generate compiler errors if there's no suitable QString::arg(const C &) overload
+ template <typename C> typename QtPrivate::QEnableIf<QtPrivate::IsSequentialContainer<C>::Value, Exception>::Type &
+ arg(const C &c) Q_DECL_NOEXCEPT
+ {
+ QString s;
+ for (int i = 0; i < c.size(); ++i) {
+ s += qSL("%1").arg(c.at(i));
+ if (i < (c.size() - 1))
+ s.append(qL1S(", "));
+ }
+ m_errorString = m_errorString.arg(s);
+ return *this;
+ }
+
+ // QStringList is always special
+ Exception &arg(const QStringList &sl) Q_DECL_NOEXCEPT
+ {
+ return arg(static_cast<QList<QString>>(sl));
+ }
+
+ // this will generate compiler errors if there's no suitable QString::arg(const Ts &) overload
template <typename... Ts> Exception &arg(const Ts & ...ts) Q_DECL_NOEXCEPT
{
m_errorString = m_errorString.arg(ts...);
diff --git a/src/common-lib/global.h b/src/common-lib/global.h
index c1172b7b..72cec409 100644
--- a/src/common-lib/global.h
+++ b/src/common-lib/global.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/logging.cpp b/src/common-lib/logging.cpp
index 064610b5..74d9a7c6 100644
--- a/src/common-lib/logging.cpp
+++ b/src/common-lib/logging.cpp
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -73,8 +74,8 @@ Q_CORE_EXPORT void qWinMsgHandler(QtMsgType t, const char* str);
QT_BEGIN_NAMESPACE_AM
#if defined(QT_GENIVIEXTRAS_LIB)
-static const char *s_defaultSystemUiDltId = "LXAM";
-static const char *s_defaultSystemUiDltDescription = "Luxoft Application-Manager";
+static const char *s_defaultSystemUiDltId = "QTAM";
+static const char *s_defaultSystemUiDltDescription = "Qt Application Manager";
#endif
/*
@@ -105,6 +106,10 @@ static const char *s_defaultSystemUiDltDescription = "Luxoft Application-Manager
\li \c QML
\li General QML related messages
\row
+ \li \c am.runtime
+ \li \c RT
+ \li Runtime messages
+\row
\li \c am.runtime.qml
\li \c QMRT
\li QML runtime messages
@@ -125,6 +130,10 @@ static const char *s_defaultSystemUiDltDescription = "Luxoft Application-Manager
\li \c INTN
\li Intent sub-system messages
\row
+ \li \c am.cache
+ \li \c CACH
+ \li Cache sub-system messages
+\row
\li \c general
\li \c GEN
\li Used for DLT logging only and enabled by default. Categories that have no context ID
@@ -139,11 +148,13 @@ QDLT_LOGGING_CATEGORY(LogInstaller, "am.installer", "INST", "Installer sub-syste
QDLT_LOGGING_CATEGORY(LogGraphics, "am.graphics", "GRPH", "OpenGL/UI related messages")
QDLT_LOGGING_CATEGORY(LogWaylandDebug, "am.wayland.debug", "WAYL", "Wayland related messages")
QDLT_LOGGING_CATEGORY(LogQml, "am.qml", "QML", "General QML related messages")
+QDLT_LOGGING_CATEGORY(LogRuntime, "am.runtime", "RT", "Runtime messages")
QDLT_LOGGING_CATEGORY(LogQmlRuntime, "am.runtime.qml", "QMRT", "QML runtime messages")
QDLT_LOGGING_CATEGORY(LogQmlIpc, "am.qml.ipc", "QMIP", "QML IPC messages")
QDLT_LOGGING_CATEGORY(LogNotifications, "am.notify", "NTFY", "Notifications sub-system messages")
QDLT_LOGGING_CATEGORY(LogDeployment, "am.deployment", "DPLM", "Deployment hints")
QDLT_LOGGING_CATEGORY(LogIntents, "am.intent", "INTN", "Intents sub-system messages")
+QDLT_LOGGING_CATEGORY(LogCache, "am.cache", "CACH", "Cache sub-system messages")
QDLT_LOGGING_CATEGORY(LogGeneral, "general", "GEN", "Messages without dedicated context ID (fallback)")
QDLT_FALLBACK_CATEGORY(LogGeneral)
@@ -391,10 +402,10 @@ QVariant Logging::useAMConsoleLogger()
void Logging::useAMConsoleLogger(const QVariant &config)
{
s_useAMConsoleLoggerConfig = config;
- if (!s_useAMConsoleLoggerConfig.isValid())
- s_useAMConsoleLogger = !s_messagePatternDefined;
- else
+ if (s_useAMConsoleLoggerConfig.userType() == QMetaType::Bool)
s_useAMConsoleLogger = s_useAMConsoleLoggerConfig.toBool();
+ else
+ s_useAMConsoleLogger = !s_messagePatternDefined;
}
QByteArray Logging::applicationId()
diff --git a/src/common-lib/logging.h b/src/common-lib/logging.h
index 7671a6cb..054d8cdb 100644
--- a/src/common-lib/logging.h
+++ b/src/common-lib/logging.h
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -53,10 +54,12 @@ Q_DECLARE_LOGGING_CATEGORY(LogGraphics)
Q_DECLARE_LOGGING_CATEGORY(LogWaylandDebug)
Q_DECLARE_LOGGING_CATEGORY(LogQml)
Q_DECLARE_LOGGING_CATEGORY(LogNotifications)
+Q_DECLARE_LOGGING_CATEGORY(LogRuntime)
Q_DECLARE_LOGGING_CATEGORY(LogQmlRuntime)
Q_DECLARE_LOGGING_CATEGORY(LogQmlIpc)
Q_DECLARE_LOGGING_CATEGORY(LogDeployment)
Q_DECLARE_LOGGING_CATEGORY(LogIntents)
+Q_DECLARE_LOGGING_CATEGORY(LogCache)
class Logging
{
diff --git a/src/common-lib/processtitle.cpp b/src/common-lib/processtitle.cpp
index c65cdb8f..e2b9401e 100644
--- a/src/common-lib/processtitle.cpp
+++ b/src/common-lib/processtitle.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/processtitle.h b/src/common-lib/processtitle.h
index cecbc150..90c83af5 100644
--- a/src/common-lib/processtitle.h
+++ b/src/common-lib/processtitle.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/qml-utilities.cpp b/src/common-lib/qml-utilities.cpp
index 7cf29393..f3d43965 100644
--- a/src/common-lib/qml-utilities.cpp
+++ b/src/common-lib/qml-utilities.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/qml-utilities.h b/src/common-lib/qml-utilities.h
index 9c9f125d..e13a1f10 100644
--- a/src/common-lib/qml-utilities.h
+++ b/src/common-lib/qml-utilities.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/qtyaml.cpp b/src/common-lib/qtyaml.cpp
index fbb75f50..f6a625ac 100644
--- a/src/common-lib/qtyaml.cpp
+++ b/src/common-lib/qtyaml.cpp
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -41,257 +42,23 @@
****************************************************************************/
#include <QVariant>
-#include <QRegExp>
+#include <QRegularExpression>
#include <QDebug>
#include <QtNumeric>
+#include <QTextCodec>
+#include <QFileInfo>
+#include <QDir>
#include <yaml.h>
#include "global.h"
#include "qtyaml.h"
+#include "exception.h"
-QT_BEGIN_NAMESPACE
+QT_BEGIN_NAMESPACE_AM
-namespace QtYaml {
-
-static QVariant convertYamlNodeToVariant(yaml_document_t *doc, yaml_node_t *node)
-{
- QVariant result;
-
- if (!doc)
- return result;
- if (!node)
- return result;
-
- switch (node->type) {
- case YAML_SCALAR_NODE: {
- const QByteArray ba = QByteArray::fromRawData(reinterpret_cast<const char *>(node->data.scalar.value),
- int(node->data.scalar.length));
-
- if (node->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE
- || node->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE) {
- result = QString::fromUtf8(ba);
- break;
- }
-
- enum ValueIndex {
- ValueNull,
- ValueTrue,
- ValueFalse,
- ValueNaN,
- ValueInf
- };
-
- struct StaticMapping
- {
- const char *text;
- ValueIndex index;
- };
-
- static QVariant staticValues[] = {
- QVariant(), // ValueNull
- QVariant(true), // ValueTrue
- QVariant(false), // ValueFalse
- QVariant(qQNaN()), // ValueNaN
- QVariant(qInf()), // ValueInf
- };
-
- static const StaticMapping staticMappings[] = { // keep this sorted for bsearch !!
- { "", ValueNull },
- { ".INF", ValueInf },
- { ".Inf", ValueInf },
- { ".NAN", ValueNaN },
- { ".NaN", ValueNaN },
- { ".inf", ValueInf },
- { ".nan", ValueNaN },
- { "FALSE", ValueFalse },
- { "False", ValueFalse },
- { "N", ValueFalse },
- { "NO", ValueFalse },
- { "NULL", ValueNull },
- { "No", ValueFalse },
- { "Null", ValueNull },
- { "OFF", ValueFalse },
- { "Off", ValueFalse },
- { "ON", ValueTrue },
- { "On", ValueTrue },
- { "TRUE", ValueTrue },
- { "True", ValueTrue },
- { "Y", ValueTrue },
- { "YES", ValueTrue },
- { "Yes", ValueTrue },
- { "false", ValueFalse },
- { "n", ValueFalse },
- { "no", ValueFalse },
- { "null", ValueNull },
- { "off", ValueFalse },
- { "on", ValueTrue },
- { "true", ValueTrue },
- { "y", ValueTrue },
- { "yes", ValueTrue },
- { "~", ValueNull }
- };
-
- static const char *firstCharStaticMappings = ".FNOTYfnoty~";
- char firstChar = ba.isEmpty() ? 0 : ba.at(0);
-
- if (strchr(firstCharStaticMappings, firstChar)) { // cheap check to avoid expensive bsearch
- StaticMapping key { ba.constData(), ValueNull };
- auto found = bsearch(&key,
- staticMappings,
- sizeof(staticMappings)/sizeof(staticMappings[0]),
- sizeof(staticMappings[0]),
- [](const void *m1, const void *m2) {
- return strcmp(static_cast<const StaticMapping *>(m1)->text,
- static_cast<const StaticMapping *>(m2)->text); });
-
- if (found) {
- result = staticValues[static_cast<StaticMapping *>(found)->index];
- break;
- }
- }
-
- QString str = QString::fromUtf8(ba);
- result = str;
-
- if ((firstChar >= '0' && firstChar <= '9') // cheap check to avoid expensive regexps
- || firstChar == '+' || firstChar == '-' || firstChar == '.') {
- static const QRegExp numberRegExps[] = {
- QRegExp(qSL("[-+]?0b[0-1_]+")), // binary
- QRegExp(qSL("[-+]?0x[0-9a-fA-F_]+")), // hexadecimal
- QRegExp(qSL("[-+]?0[0-7_]+")), // octal
- QRegExp(qSL("[-+]?(0|[1-9][0-9_]*)")), // decimal
- QRegExp(qSL("[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?")), // float
- QRegExp()
- };
-
- for (int numberIndex = 0; !numberRegExps[numberIndex].isEmpty(); ++numberIndex) {
- if (numberRegExps[numberIndex].exactMatch(str)) {
- bool ok = false;
- QVariant val;
-
- // YAML allows _ as a grouping separator
- if (str.contains(qL1C('_')))
- str = str.replace(qL1C('_'), qSL(""));
-
- if (numberIndex == 4) {
- val = str.toDouble(&ok);
- } else {
- int base = 10;
-
- switch (numberIndex) {
- case 0: base = 2; str.replace(qSL("0b"), qSL("")); break; // Qt chokes on 0b
- case 1: base = 16; break;
- case 2: base = 8; break;
- case 3: base = 10; break;
- }
-
- qint64 s64 = str.toLongLong(&ok, base);
- if (ok && (s64 <= std::numeric_limits<qint32>::max())) {
- val = qint32(s64);
- } else if (ok) {
- val = s64;
- } else {
- quint64 u64 = str.toULongLong(&ok, base);
-
- if (ok && (u64 <= std::numeric_limits<quint32>::max()))
- val = quint32(u64);
- else if (ok)
- val = u64;
- }
- }
- if (ok) {
- result = val;
- break;
- }
- }
- }
- }
- break;
- }
- case YAML_SEQUENCE_NODE: {
- QVariantList array;
- for (auto seq = node->data.sequence.items.start; seq < node->data.sequence.items.top; ++seq) {
- yaml_node_t *seqNode = yaml_document_get_node(doc, *seq);
- if (seqNode)
- array.append(convertYamlNodeToVariant(doc, seqNode));
- else
- array.append(QVariant());
- }
- result = array;
- break;
- }
- case YAML_MAPPING_NODE: {
- QVariantMap object;
- for (auto map = node->data.mapping.pairs.start; map < node->data.mapping.pairs.top; ++map) {
- yaml_node_t *keyNode = yaml_document_get_node(doc, map->key);
- yaml_node_t *valueNode = yaml_document_get_node(doc, map->value);
- if (keyNode && valueNode) {
- QVariant key = convertYamlNodeToVariant(doc, keyNode);
- QString keyStr = key.toString();
-
- if (key.type() != QVariant::String)
- qWarning() << "YAML Parser: converting non-string mapping key to string for JSON compatibility";
- if (object.contains(keyStr))
- qWarning() << "YAML Parser: duplicate key" << keyStr << "found in mapping";
-
- object.insert(keyStr, convertYamlNodeToVariant(doc, valueNode));
- }
- }
- result = object;
- break;
- }
- default:
- break;
- }
-
- return result;
-}
-
-
-QVector<QVariant> variantDocumentsFromYaml(const QByteArray &yaml, ParseError *error)
-{
- QVector<QVariant> result;
-
- if (error)
- *error = ParseError();
-
- yaml_parser_t p;
- if (yaml_parser_initialize(&p)) {
- yaml_parser_set_input_string(&p, reinterpret_cast<const uchar *>(yaml.constData()),
- static_cast<size_t>(yaml.size()));
-
- yaml_document_t doc;
- yaml_node_t *root;
- do {
- if (!yaml_parser_load(&p, &doc)) {
- if (error) {
- switch (p.error) {
- case YAML_READER_ERROR:
- *error = ParseError(QString::fromLocal8Bit(p.problem), -1, -1, int(p.problem_offset));
- break;
- case YAML_SCANNER_ERROR:
- case YAML_PARSER_ERROR:
- *error = ParseError(QString::fromLocal8Bit(p.problem), int(p.problem_mark.line + 1), int(p.problem_mark.column), int(p.problem_mark.index));
- break;
- default:
- break;
- }
- }
- }
- root = yaml_document_get_root_node(&doc);
- if (root)
- result.append(convertYamlNodeToVariant(&doc, root));
- yaml_document_delete(&doc);
- } while (root);
-
- yaml_parser_delete(&p);
- } else if (error) {
- *error = ParseError(qSL("could not initialize YAML parser"));
- }
- return result;
-}
+namespace QtYaml {
static inline void yerr(int result) Q_DECL_NOEXCEPT_EXPR(false)
{
@@ -412,6 +179,499 @@ QByteArray yamlFromVariantDocuments(const QVector<QVariant> &documents, YamlStyl
return out;
}
+
} // namespace QtYaml
-QT_END_NAMESPACE
+
+class YamlParserPrivate
+{
+public:
+ QString sourceName;
+ QString sourceDir;
+ QByteArray data;
+ QTextCodec *codec = nullptr;
+ bool parsedHeader = false;
+ yaml_parser_t parser;
+ yaml_event_t event;
+
+};
+
+
+YamlParser::YamlParser(const QByteArray &data, const QString &fileName)
+ : d(new YamlParserPrivate)
+{
+ d->data = data;
+ if (!fileName.isEmpty()) {
+ QFileInfo fi(fileName);
+ d->sourceDir = fi.absolutePath();
+ d->sourceName = fi.fileName();
+ }
+
+ memset(&d->parser, 0, sizeof(d->parser));
+ memset(&d->event, 0, sizeof(d->event));
+
+ if (!yaml_parser_initialize(&d->parser))
+ throw Exception("Failed to initialize YAML parser");
+ yaml_parser_set_input_string(&d->parser, reinterpret_cast<const unsigned char *>(d->data.constData()),
+ static_cast<size_t>(d->data.size()));
+ nextEvent();
+ if (d->event.type != YAML_STREAM_START_EVENT)
+ throw Exception("Invalid YAML data");
+ switch (d->event.data.stream_start.encoding) {
+ default:
+ case YAML_UTF8_ENCODING: d->codec = QTextCodec::codecForName("UTF-8"); break;
+ case YAML_UTF16LE_ENCODING: d->codec = QTextCodec::codecForName("UTF-16LE"); break;
+ case YAML_UTF16BE_ENCODING: d->codec = QTextCodec::codecForName("UTF-16BE"); break;
+ }
+}
+
+YamlParser::~YamlParser()
+{
+ if (d->event.type != YAML_NO_EVENT)
+ yaml_event_delete(&d->event);
+ yaml_parser_delete(&d->parser);
+ delete d;
+}
+
+QString YamlParser::sourcePath() const
+{
+ return QDir(sourceDir()).absoluteFilePath(sourceName());
+}
+
+QString YamlParser::sourceDir() const
+{
+ return d->sourceDir;
+}
+
+QString YamlParser::sourceName() const
+{
+ return d->sourceName;
+}
+
+QVector<QVariant> YamlParser::parseAllDocuments(const QByteArray &yaml)
+{
+ YamlParser p(yaml);
+ QVector<QVariant> result;
+ while (p.nextDocument())
+ result << p.parseMap();
+ return result;
+}
+
+QPair<QString, int> YamlParser::parseHeader()
+{
+ if (d->parsedHeader)
+ throw Exception("Already parsed header");
+ if (d->event.type != YAML_STREAM_START_EVENT)
+ throw Exception("Header must be the first document of a file");
+ if (!nextDocument())
+ throw Exception("No header document available");
+ if (!isMap())
+ throw Exception("Header document is not a map");
+
+ QPair<QString, int> result;
+
+ Fields fields = {
+ { "formatType", true, YamlParser::Scalar, [&result](YamlParser *parser) {
+ result.first = parser->parseScalar().toString(); } },
+ { "formatVersion", true, YamlParser::Scalar, [&result](YamlParser *parser) {
+ result.second = parser->parseScalar().toInt(); } }
+ };
+
+ parseFields(fields);
+
+ d->parsedHeader = true;
+ return result;
+}
+
+bool YamlParser::nextDocument()
+{
+ while (d->event.type != YAML_STREAM_END_EVENT) {
+ nextEvent();
+ if (d->event.type == YAML_DOCUMENT_START_EVENT) {
+ nextEvent(); // advance to the first child or end-of-document
+ return true;
+ }
+ }
+ return false;
+}
+
+void YamlParser::nextEvent()
+{
+ if (d->event.type != YAML_NO_EVENT)
+ yaml_event_delete(&d->event);
+
+ do {
+ if (!yaml_parser_parse(&d->parser, &d->event))
+ throw YamlParserException(this, "invalid YAML syntax");
+ if (d->event.type == YAML_ALIAS_EVENT)
+ throw YamlParserException(this, "anchors and aliases are not supported");
+ } while (d->event.type == YAML_NO_EVENT);
+}
+
+bool YamlParser::isScalar() const
+{
+ return d->event.type == YAML_SCALAR_EVENT;
+}
+
+QString YamlParser::parseString() const
+{
+ if (!isScalar())
+ return QString{};
+
+ Q_ASSERT(d->event.data.scalar.value);
+ Q_ASSERT(static_cast<int>(d->event.data.scalar.length) >= 0);
+
+ return QTextDecoder(d->codec).toUnicode(reinterpret_cast<const char *>(d->event.data.scalar.value),
+ static_cast<int>(d->event.data.scalar.length));
+}
+
+QVariant YamlParser::parseScalar() const
+{
+ QString scalar = parseString();
+
+ if (scalar.isEmpty()
+ || d->event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE
+ || d->event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE) {
+ return scalar;
+ }
+
+ enum ValueIndex {
+ ValueNull,
+ ValueTrue,
+ ValueFalse,
+ ValueNaN,
+ ValueInf
+ };
+
+ struct StaticMapping
+ {
+ QString text;
+ ValueIndex index;
+ };
+
+ static const QVariant staticValues[] = {
+ QVariant::fromValue(nullptr), // ValueNull
+ QVariant(true), // ValueTrue
+ QVariant(false), // ValueFalse
+ QVariant(qQNaN()), // ValueNaN
+ QVariant(qInf()), // ValueInf
+ };
+
+ static const StaticMapping staticMappings[] = { // keep this sorted for bsearch !!
+ { qSL(""), ValueNull },
+ { qSL(".INF"), ValueInf },
+ { qSL(".Inf"), ValueInf },
+ { qSL(".NAN"), ValueNaN },
+ { qSL(".NaN"), ValueNaN },
+ { qSL(".inf"), ValueInf },
+ { qSL(".nan"), ValueNaN },
+ { qSL("FALSE"), ValueFalse },
+ { qSL("False"), ValueFalse },
+ { qSL("N"), ValueFalse },
+ { qSL("NO"), ValueFalse },
+ { qSL("NULL"), ValueNull },
+ { qSL("No"), ValueFalse },
+ { qSL("Null"), ValueNull },
+ { qSL("OFF"), ValueFalse },
+ { qSL("Off"), ValueFalse },
+ { qSL("ON"), ValueTrue },
+ { qSL("On"), ValueTrue },
+ { qSL("TRUE"), ValueTrue },
+ { qSL("True"), ValueTrue },
+ { qSL("Y"), ValueTrue },
+ { qSL("YES"), ValueTrue },
+ { qSL("Yes"), ValueTrue },
+ { qSL("false"), ValueFalse },
+ { qSL("n"), ValueFalse },
+ { qSL("no"), ValueFalse },
+ { qSL("null"), ValueNull },
+ { qSL("off"), ValueFalse },
+ { qSL("on"), ValueTrue },
+ { qSL("true"), ValueTrue },
+ { qSL("y"), ValueTrue },
+ { qSL("yes"), ValueTrue },
+ { qSL("~"), ValueNull }
+ };
+
+ static const char *firstCharStaticMappings = ".FNOTYfnoty~";
+ char firstChar = scalar.isEmpty() ? 0 : scalar.at(0).toLatin1();
+
+ if (strchr(firstCharStaticMappings, firstChar)) { // cheap check to avoid expensive bsearch
+ StaticMapping key { scalar, ValueNull };
+ auto found = bsearch(&key,
+ staticMappings,
+ sizeof(staticMappings) / sizeof(staticMappings[0]),
+ sizeof(staticMappings[0]),
+ [](const void *m1, const void *m2) {
+ return static_cast<const StaticMapping *>(m1)->text.compare(static_cast<const StaticMapping *>(m2)->text);
+ });
+
+ if (found)
+ return staticValues[static_cast<StaticMapping *>(found)->index];
+ }
+
+ if ((firstChar >= '0' && firstChar <= '9') // cheap check to avoid expensive regexps
+ || firstChar == '+' || firstChar == '-' || firstChar == '.') {
+ static const QRegularExpression numberRegExps[] = {
+ QRegularExpression(qSL("\\A[-+]?(0|[1-9][0-9_]*)\\z")), // decimal
+ QRegularExpression(qSL("\\A[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?\\z")), // float
+ QRegularExpression(qSL("\\A[-+]?0x[0-9a-fA-F_]+\\z")), // hexadecimal
+ QRegularExpression(qSL("\\A[-+]?0b[0-1_]+\\z")), // binary
+ QRegularExpression(qSL("\\A[-+]?0[0-7_]+\\z")), // octal
+ };
+ for (size_t numberIndex = 0; numberIndex < (sizeof(numberRegExps) / sizeof(*numberRegExps)); ++numberIndex) {
+ if (numberRegExps[numberIndex].match(scalar).hasMatch()) {
+ bool ok = false;
+ QVariant val;
+
+ // YAML allows _ as a grouping separator
+ if (scalar.contains(qL1C('_')))
+ scalar = scalar.replace(qL1C('_'), qSL(""));
+
+ if (numberIndex == 1) {
+ val = scalar.toDouble(&ok);
+ } else {
+ int base = 10;
+
+ switch (numberIndex) {
+ case 0: base = 10; break;
+ case 2: base = 16; break;
+ case 3: base = 2; scalar.replace(qSL("0b"), qSL("")); break; // Qt chokes on 0b
+ case 4: base = 8; break;
+ }
+
+ qint64 s64 = scalar.toLongLong(&ok, base);
+ if (ok && (s64 <= std::numeric_limits<qint32>::max())) {
+ val = qint32(s64);
+ } else if (ok) {
+ val = s64;
+ } else {
+ quint64 u64 = scalar.toULongLong(&ok, base);
+
+ if (ok && (u64 <= std::numeric_limits<quint32>::max()))
+ val = quint32(u64);
+ else if (ok)
+ val = u64;
+ }
+ }
+ if (ok)
+ return val;
+ }
+ }
+ }
+ return scalar;
+}
+
+bool YamlParser::isMap() const
+{
+ return d->event.type == YAML_MAPPING_START_EVENT;
+}
+
+QString YamlParser::parseMapKey()
+{
+ if (isScalar()) {
+ QVariant key = parseScalar();
+ if (key.type() == QVariant::String)
+ return key.toString();
+ }
+ throw YamlParserException(this, "Only strings are supported as mapping keys");
+}
+
+QVariantMap YamlParser::parseMap()
+{
+ if (!isMap())
+ return QVariantMap {};
+
+ QVariantMap map;
+ while (true) {
+ nextEvent(); // read key
+ if (d->event.type == YAML_MAPPING_END_EVENT)
+ return map;
+
+ QString key = parseMapKey();
+ if (map.contains(key))
+ throw YamlParserException(this, "Found duplicate key '%1' in mapping").arg(key);
+
+ nextEvent(); // read value
+ QVariant value = parseVariant();
+
+ map.insert(key, value);
+ }
+}
+
+bool YamlParser::isList() const
+{
+ return d->event.type == YAML_SEQUENCE_START_EVENT;
+}
+
+void YamlParser::parseList(const std::function<void(YamlParser *p)> &callback)
+{
+ if (!isList())
+ throw YamlParserException(this, "Cannot parse non-list as list");
+
+ while (true) {
+ nextEvent(); // read next value
+
+ if (d->event.type == YAML_SEQUENCE_END_EVENT)
+ return;
+
+ switch (d->event.type) {
+ case YAML_SCALAR_EVENT:
+ case YAML_MAPPING_START_EVENT:
+ case YAML_SEQUENCE_START_EVENT:
+ callback(this);
+ break;
+ default:
+ throw YamlParserException(this, "Unexpected event (%1) encountered while parsing a list").arg(d->event.type);
+ }
+ }
+}
+
+QVariant YamlParser::parseVariant()
+{
+ QVariant value;
+ if (isScalar()) {
+ value = parseScalar();
+ } else if (isList()) {
+ value = parseList();
+ } else if (isMap()) {
+ value = parseMap();
+ } else {
+ throw YamlParserException(this, "Unexpected event (%1) encountered while parsing a variant")
+ .arg(d->event.type);
+ }
+ return value;
+}
+
+QVariantList YamlParser::parseList()
+{
+ QVariantList list;
+ parseList([&list](YamlParser *p) {
+ list.append(p->parseVariant());
+ });
+ return list;
+}
+
+QStringList YamlParser::parseStringOrStringList()
+{
+ if (isList()) {
+ QStringList result;
+ parseList([&result](YamlParser *p) {
+ if (!p->isScalar())
+ throw YamlParserException(p, "A list or map is not a valid member of a string-list");
+ result.append(p->parseString());
+ });
+ return result;
+ } else if (isScalar()) {
+ return { parseString() };
+ } else {
+ throw YamlParserException(this, "Cannot parse a map as string or string-list");
+ }
+}
+
+void YamlParser::parseFields(const std::vector<Field> &fields)
+{
+ if (!isMap())
+ throw YamlParserException(this, "Expected a map (type %1) to parse fields from, but got type %2")
+ .arg(YAML_MAPPING_START_EVENT).arg(d->event.type);
+
+ QVector<QString> fieldsFound;
+
+ while (true) {
+ nextEvent(); // read key
+ if (d->event.type == YAML_MAPPING_END_EVENT)
+ break;
+ QString key = parseMapKey();
+ if (fieldsFound.contains(key))
+ throw YamlParserException(this, "Found duplicate key '%1' in mapping").arg(key);
+
+ auto field = fields.cbegin();
+ for (; field != fields.cend(); ++field) {
+ if (key == qL1S(field->name))
+ break;
+ }
+ if (field == fields.cend())
+ throw YamlParserException(this, "Field '%1' is not valid in this context").arg(key);
+ fieldsFound << key;
+
+ nextEvent(); // read value
+ QVector<yaml_event_type_t> allowedEvents;
+ if (field->types & YamlParser::Scalar)
+ allowedEvents.append(YAML_SCALAR_EVENT);
+ if (field->types & YamlParser::Map)
+ allowedEvents.append(YAML_MAPPING_START_EVENT);
+ if (field->types & YamlParser::List)
+ allowedEvents.append(YAML_SEQUENCE_START_EVENT);
+
+ if (!allowedEvents.contains(d->event.type)) { // ALIASES MISSING HERE!
+ auto mapEventNames = [](const QVector<yaml_event_type_t> &events) -> QString {
+ static const QHash<yaml_event_type_t, const char *> eventNames = {
+ { YAML_NO_EVENT, "nothing" },
+ { YAML_STREAM_START_EVENT, "stream start" },
+ { YAML_STREAM_END_EVENT, "stream end" },
+ { YAML_DOCUMENT_START_EVENT, "document start" },
+ { YAML_DOCUMENT_END_EVENT, "document end" },
+ { YAML_ALIAS_EVENT, "alias" },
+ { YAML_SCALAR_EVENT, "scalar" },
+ { YAML_SEQUENCE_START_EVENT, "sequence start" },
+ { YAML_SEQUENCE_END_EVENT, "sequence end" },
+ { YAML_MAPPING_START_EVENT, "mapping start" },
+ { YAML_MAPPING_END_EVENT, "mapping end" }
+ };
+ QString names;
+ for (int i = 0; i < events.size(); ++i) {
+ if (i)
+ names.append(i == (events.size() - 1) ? qL1S(" or ") : qL1S(", "));
+ names.append(qL1S(eventNames.value(events.at(i), "<unknown>")));
+ }
+ return names;
+ };
+
+ throw YamlParserException(this, "Field '%1' expected to be of type '%2', but got '%3'")
+ .arg(field->name).arg(mapEventNames(allowedEvents)).arg(mapEventNames({ d->event.type }));
+ }
+
+ yaml_event_type_t typeBefore = d->event.type;
+ yaml_event_type_t typeAfter;
+ switch (typeBefore) {
+ case YAML_MAPPING_START_EVENT: typeAfter = YAML_MAPPING_END_EVENT; break;
+ case YAML_SEQUENCE_START_EVENT: typeAfter = YAML_SEQUENCE_END_EVENT; break;
+ default: typeAfter = typeBefore; break;
+ }
+ field->callback(this);
+ if (d->event.type != typeAfter) {
+ throw YamlParserException(this, "Invalid YAML event state after field callback for '%3': expected %1, but got %2")
+ .arg(typeAfter).arg(d->event.type).arg(key);
+ }
+ }
+ QStringList fieldsMissing;
+ for (auto field : fields) {
+ if (field.required && !fieldsFound.contains(qL1S(field.name)))
+ fieldsMissing.append(qL1S(field.name));
+ }
+ if (!fieldsMissing.isEmpty())
+ throw YamlParserException(this, "Required fields are missing: %1").arg(fieldsMissing);
+}
+
+YamlParserException::YamlParserException(YamlParser *p, const char *errorString)
+ : Exception(Error::Parse, "YAML parse error")
+{
+ bool isProblem = p->d->parser.problem;
+ yaml_mark_t &mark = isProblem ? p->d->parser.problem_mark : p->d->parser.mark;
+
+ QString context = QTextDecoder(p->d->codec).toUnicode(p->d->data);
+ int lpos = context.lastIndexOf(qL1C('\n'), int(mark.index ? mark.index - 1 : 0));
+ int rpos = context.indexOf(qL1C('\n'), int(mark.index));
+ context = context.mid(lpos + 1, rpos == -1 ? context.size() : rpos - lpos - 1);
+ int contextPos = int(mark.index) - (lpos + 1);
+
+ m_errorString.append(qSL(":\nfile://%1:%2:%3: error").arg(p->sourcePath()).arg(mark.line + 1).arg(mark.column + 1));
+ if (errorString)
+ m_errorString.append(qSL(": %1").arg(qL1S(errorString)));
+ if (isProblem)
+ m_errorString.append(qSL(": %1").arg(QString::fromUtf8(p->d->parser.problem)));
+ if (!context.isEmpty())
+ m_errorString.append(qSL("\n %1\n %2^").arg(context, QString(contextPos, qL1C(' '))));
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/common-lib/qtyaml.h b/src/common-lib/qtyaml.h
index 11edb200..33d51ec9 100644
--- a/src/common-lib/qtyaml.h
+++ b/src/common-lib/qtyaml.h
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -43,14 +44,17 @@
#pragma once
#include <functional>
+#include <vector>
#include <QJsonParseError>
#include <QVector>
#include <QByteArray>
#include <QString>
#include <QVariant>
+#include <QtAppManCommon/global.h>
+#include <QtAppManCommon/exception.h>
-QT_BEGIN_NAMESPACE
+QT_BEGIN_NAMESPACE_AM
namespace QtYaml {
@@ -89,4 +93,78 @@ QByteArray yamlFromVariantDocuments(const QVector<QVariant> &maps, YamlStyle sty
} // namespace QtYaml
-QT_END_NAMESPACE
+class YamlParserPrivate;
+class YamlParserException;
+
+class YamlParser
+{
+public:
+ YamlParser(const QByteArray &data, const QString &fileName = QString());
+ ~YamlParser();
+
+ QString sourcePath() const;
+ QString sourceDir() const;
+ QString sourceName() const;
+
+ static QVector<QVariant> parseAllDocuments(const QByteArray &yaml);
+
+ QPair<QString, int> parseHeader();
+
+ bool nextDocument();
+ void nextEvent();
+
+ bool isScalar() const;
+ QVariant parseScalar() const;
+ QString parseString() const;
+
+ bool isMap() const;
+ QVariantMap parseMap();
+
+ bool isList() const;
+ QVariantList parseList();
+ void parseList(const std::function<void (YamlParser *)> &callback);
+
+ // convenience
+ QVariant parseVariant();
+ QStringList parseStringOrStringList();
+
+ enum FieldType { Scalar = 0x01, List = 0x02, Map = 0x04 };
+ Q_DECLARE_FLAGS(FieldTypes, FieldType)
+ struct Field
+ {
+ QByteArray name;
+ bool required;
+ FieldTypes types;
+ std::function<void(YamlParser *)> callback;
+
+ Field(const char *_name, bool _required, FieldTypes _types,
+ const std::function<void(YamlParser *)> &_callback)
+ : name(_name)
+ , required(_required)
+ , types(_types)
+ , callback(_callback)
+ { }
+ };
+ typedef std::vector<Field> Fields;
+
+ void parseFields(const Fields &fields);
+
+private:
+ Q_DISABLE_COPY(YamlParser)
+
+ QString parseMapKey();
+
+ YamlParserPrivate *d;
+ friend class YamlParserException;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(YamlParser::FieldTypes)
+
+class YamlParserException : public Exception
+{
+public:
+ explicit YamlParserException(YamlParser *p, const char *errorString);
+};
+
+QT_END_NAMESPACE_AM
+// We mean it. Dummy comment since syncqt needs this also for completely private Qt modules.
diff --git a/src/common-lib/startuptimer.cpp b/src/common-lib/startuptimer.cpp
index 861df663..5e143093 100644
--- a/src/common-lib/startuptimer.cpp
+++ b/src/common-lib/startuptimer.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -329,14 +329,15 @@ StartupTimer::StartupTimer()
size_t procInfoSize;
if (sysctl(mibNames, sizeof(mibNames) / sizeof(mibNames[0]), nullptr, &procInfoSize, nullptr, 0) == 0) {
- kinfo_proc *procInfo = (kinfo_proc *) malloc(procInfoSize);
+ kinfo_proc *procInfo = static_cast<kinfo_proc *>(malloc(procInfoSize));
if (sysctl(mibNames, sizeof(mibNames) / sizeof(mibNames[0]), procInfo, &procInfoSize, nullptr, 0) == 0) {
struct timeval now;
if (gettimeofday(&now, nullptr) == 0) {
- m_processCreation = (quint64(now.tv_sec) * 1000000 + now.tv_usec)
- - (procInfo->kp_proc.p_un.__p_starttime.tv_sec * 1000000 + procInfo->kp_proc.p_un.__p_starttime.tv_usec);
+ m_processCreation = (quint64(now.tv_sec) * 1000000 + quint64(now.tv_usec))
+ - (quint64(procInfo->kp_proc.p_un.__p_starttime.tv_sec) * 1000000
+ + quint64(procInfo->kp_proc.p_un.__p_starttime.tv_usec));
m_initialized = true;
}
} else {
@@ -353,7 +354,7 @@ StartupTimer::StartupTimer()
size_t bootTimeLen = sizeof(bootTime);
int mibNames[2] = { CTL_KERN, KERN_BOOTTIME };
if (sysctl(mibNames, sizeof(mibNames) / sizeof(mibNames[0]), &bootTime, &bootTimeLen, nullptr, 0) == 0 ) {
- m_systemUpTime = (time(nullptr) - bootTime.tv_sec) * 1000; // we don't need more precision on macOS
+ m_systemUpTime = quint64(time(nullptr) - bootTime.tv_sec) * 1000; // we don't need more precision on macOS
emit systemUpTimeChanged(m_systemUpTime);
}
}
@@ -450,10 +451,9 @@ void StartupTimer::createReport(const QString &title)
if (m_output == stderr)
getOutputInformation(&ansiColorSupport, nullptr, nullptr);
- const char *format = "\n== STARTUP TIMING REPORT: %s ==\n";
- if (ansiColorSupport)
- format = "\n\033[33m== STARTUP TIMING REPORT: %s ==\033[0m\n";
- fprintf(m_output, format, title.toLocal8Bit().data());
+ constexpr const char *plainFormat = "\n== STARTUP TIMING REPORT: %s ==\n";
+ constexpr const char *colorFormat = "\n\033[33m== STARTUP TIMING REPORT: %s ==\033[0m\n";
+ fprintf(m_output, ansiColorSupport ? colorFormat : plainFormat, title.toLocal8Bit().data());
static const int barCols = 60;
@@ -475,12 +475,11 @@ void StartupTimer::createReport(const QString &title)
QByteArray spacing(maxTextLen - text.length(), ' ');
SplitSeconds ss = splitMicroSecs(usec);
- const char *format = "%d'%03d.%03d %s %s#%s\n";
- if (ansiColorSupport)
- format = "\033[32m%d'%03d.%03d\033[0m %s %s\033[44m %s\033[0m\n";
+ constexpr const char *plainFormat = "%d'%03d.%03d %s %s#%s\n";
+ constexpr const char *colorFormat = "\033[32m%d'%03d.%03d\033[0m %s %s\033[44m %s\033[0m\n";
- fprintf(m_output, format, ss.sec, ss.msec, ss.usec,
- text.constData(), spacing.constData(), bar.constData());
+ fprintf(m_output, ansiColorSupport ? colorFormat : plainFormat,
+ ss.sec, ss.msec, ss.usec, text.constData(), spacing.constData(), bar.constData());
}
fflush(m_output);
diff --git a/src/common-lib/startuptimer.h b/src/common-lib/startuptimer.h
index ab2ead55..89f9282e 100644
--- a/src/common-lib/startuptimer.h
+++ b/src/common-lib/startuptimer.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/unixsignalhandler.cpp b/src/common-lib/unixsignalhandler.cpp
index 897a3103..cd51fae3 100644
--- a/src/common-lib/unixsignalhandler.cpp
+++ b/src/common-lib/unixsignalhandler.cpp
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/unixsignalhandler.h b/src/common-lib/unixsignalhandler.h
index da2fd0d5..f1de44f1 100644
--- a/src/common-lib/unixsignalhandler.h
+++ b/src/common-lib/unixsignalhandler.h
@@ -4,7 +4,7 @@
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
diff --git a/src/common-lib/utilities.cpp b/src/common-lib/utilities.cpp
index b489fe56..c70d6abb 100644
--- a/src/common-lib/utilities.cpp
+++ b/src/common-lib/utilities.cpp
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -104,43 +105,63 @@ QT_BEGIN_NAMESPACE_AM
Check a YAML document against the "standard" AM header.
If \a numberOfDocuments is positive, the number of docs need to match exactly. If it is
negative, the \a numberOfDocuments is taken as the required minimum amount of documents.
-
+ Otherwise, the amount of documents is irrelevant.
*/
-void checkYamlFormat(const QVector<QVariant> &docs, int numberOfDocuments,
- const QVector<QByteArray> &formatTypes, int formatVersion) Q_DECL_NOEXCEPT_EXPR(false)
+YamlFormat checkYamlFormat(const QVector<QVariant> &docs, int numberOfDocuments,
+ const QVector<YamlFormat> &formatTypesAndVersions) Q_DECL_NOEXCEPT_EXPR(false)
{
int actualSize = docs.size();
- QByteArray actualFormatType;
- int actualFormatVersion = 0;
-
- if (actualSize >= 1) {
- const auto map = docs.constFirst().toMap();
- actualFormatType = map.value(qSL("formatType")).toString().toUtf8();
- actualFormatVersion = map.value(qSL("formatVersion")).toInt();
- }
+ if (actualSize < 1)
+ throw Exception("no header YAML document found");
if (numberOfDocuments < 0) {
- if (actualSize < numberOfDocuments) {
+ if (actualSize < -numberOfDocuments) {
throw Exception("wrong number of YAML documents: expected at least %1, got %2")
.arg(-numberOfDocuments).arg(actualSize);
}
- } else {
+ } else if (numberOfDocuments > 0) {
if (actualSize != numberOfDocuments) {
throw Exception("wrong number of YAML documents: expected %1, got %2")
.arg(numberOfDocuments).arg(actualSize);
}
}
- if (!formatTypes.contains(actualFormatType)) {
- throw Exception("wrong formatType header: expected %1, got %2")
- .arg(QString::fromUtf8(formatTypes.toList().join(", or ")), QString::fromUtf8(actualFormatType));
- }
- if (actualFormatVersion != formatVersion) {
- throw Exception("wrong formatVersion header: expected %1, got %2")
- .arg(formatVersion).arg(actualFormatVersion);
+
+ const auto map = docs.constFirst().toMap();
+ YamlFormat actualFormatTypeAndVersion = {
+ map.value(qSL("formatType")).toString(),
+ map.value(qSL("formatVersion")).toInt()
+ };
+
+ class StringifyTypeAndVersion
+ {
+ public:
+ StringifyTypeAndVersion() = default;
+ StringifyTypeAndVersion(const QPair<QString, int> &typeAndVersion)
+ {
+ operator()(typeAndVersion);
+ }
+ QString string() const
+ {
+ return m_str;
+ }
+ void operator()(const QPair<QString, int> &typeAndVersion)
+ {
+ if (!m_str.isEmpty())
+ m_str += qSL(", or ");
+ m_str = m_str + typeAndVersion.first + qSL(" version ") + QString::number(typeAndVersion.second);
+ }
+ private:
+ QString m_str;
+ };
+
+ if (!formatTypesAndVersions.contains(actualFormatTypeAndVersion)) {
+ throw Exception("wrong header: expected %1, got %2")
+ .arg(std::for_each(formatTypesAndVersions.cbegin(), formatTypesAndVersions.cend(), StringifyTypeAndVersion()).string())
+ .arg(StringifyTypeAndVersion(actualFormatTypeAndVersion).string());
}
+ return actualFormatTypeAndVersion;
}
-
QMultiMap<QString, QString> mountedDirectories()
{
QMultiMap<QString, QString> result;
diff --git a/src/common-lib/utilities.h b/src/common-lib/utilities.h
index 9910ce1c..dd07913c 100644
--- a/src/common-lib/utilities.h
+++ b/src/common-lib/utilities.h
@@ -1,10 +1,11 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the Luxoft Application Manager.
+** This file is part of the Qt Application Manager.
**
** $QT_BEGIN_LICENSE:LGPL-QTAS$
** Commercial License Usage
@@ -43,9 +44,15 @@
#pragma once
#include <QVector>
+#include <QPair>
#include <QByteArray>
#include <QMultiMap>
#include <QVariant>
+#include <QString>
+#include <QUrl>
+#include <QDir>
+#include <QResource>
+#include <QLibrary>
#include <QtAppManCommon/global.h>
@@ -57,8 +64,10 @@ QT_BEGIN_NAMESPACE_AM
int timeoutFactor();
-void checkYamlFormat(const QVector<QVariant> &docs, int numberOfDocuments,
- const QVector<QByteArray> &formatTypes, int formatVersion) Q_DECL_NOEXCEPT_EXPR(false);
+using YamlFormat = QPair<QString, int>;
+
+YamlFormat checkYamlFormat(const QVector<QVariant> &docs, int numberOfDocuments,
+ const QVector<YamlFormat> &formatTypesAndVersions) Q_DECL_NOEXCEPT_EXPR(false);
/*! \internal
Convenience function that makes it easy to accept a plain string where
@@ -70,6 +79,28 @@ inline QStringList variantToStringList(const QVariant &v)
: v.toStringList();
}
+// Translate between QFile and QUrl (resource) representations.
+// For some weird reason, QFile cannot cope with "qrc:" and QUrl cannot cope with ":".
+inline QUrl filePathToUrl(const QString &path, const QString &baseDir)
+{
+ return path.startsWith(qSL(":")) ? QUrl(qSL("qrc") + path)
+ : QUrl::fromUserInput(path, baseDir, QUrl::AssumeLocalFile);
+}
+
+inline QString urlToLocalFilePath(const QUrl &url)
+{
+ if (url.isLocalFile())
+ return url.toLocalFile();
+ else if (url.scheme() == qSL("qrc"))
+ return qL1C(':') + url.path();
+ return QString();
+}
+
+inline QString toAbsoluteFilePath(const QString &path, const QString &baseDir = QDir::currentPath())
+{
+ return path.startsWith(qSL("qrc:")) ? path.mid(3) : QDir(baseDir).absoluteFilePath(path);
+}
+
/*! \internal
Recursively merge the second QVariantMap into the first one
*/
@@ -120,4 +151,12 @@ QVector<T *> loadPlugins(const char *type, const QStringList &files) Q_DECL_NOEX
return result;
}
+// Load a Qt resource, either in the form of a resource file or a plugin
+inline bool loadResource(const QString &resource)
+{
+ return QLibrary::isLibrary(resource)
+ ? (QLibrary(QDir().absoluteFilePath(resource)).load() || QResource::registerResource(resource))
+ : (QResource::registerResource(resource) || QLibrary(QDir().absoluteFilePath(resource)).load());
+}
+
QT_END_NAMESPACE_AM