summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@qt.io>2019-10-19 00:15:08 +0200
committerRobert Griebl <robert.griebl@pelagicore.com>2019-11-07 16:42:13 +0100
commita69651ca9b3cccc4f48dfa5695498149d6bfdc43 (patch)
treea61d73a0abc23742a3721a5d0e21cf6f426753e3
parent18bf370fb0e510fb3ed516818f793523153204bb (diff)
Fully transparent and generic cache for config and app database
The existing caching mechanism for config files was abstracted and extended, so that it could be re-used to parse and cache the info.yaml files. This results in a much more robust application database and should get rid of 99.9% of all the -r/--recreate-database option usages. Change-Id: Ic37fcd3b0e55d481712d469b6e331b9c433a9c5d Reviewed-by: Dominik Holland <dominik.holland@qt.io>
-rwxr-xr-xbenchmarks/appman-bench/run.sh3
-rw-r--r--doc/configuration.qdoc38
-rw-r--r--doc/installation.qdoc7
-rw-r--r--doc/write-applications.qdoc4
-rw-r--r--examples/applicationmanager/animated-windows/animated-windows.pro2
-rw-r--r--examples/applicationmanager/custom-appman/custom-appman.cpp5
-rw-r--r--examples/applicationmanager/frame-timer/frame-timer.pro2
-rw-r--r--examples/applicationmanager/minidesk/doc/src/minidesk.qdoc3
-rw-r--r--examples/applicationmanager/multi-views/multi-views.pro2
-rw-r--r--examples/applicationmanager/process-status/process-status.pro2
-rw-r--r--qmake-features/am-qml-testcase.prf2
-rw-r--r--src/application-lib/installationreport.cpp2
-rw-r--r--src/application-lib/packagedatabase.cpp128
-rw-r--r--src/application-lib/packagedatabase.h7
-rw-r--r--src/application-lib/packageinfo.cpp18
-rw-r--r--src/application-lib/packagescanner.h5
-rw-r--r--src/application-lib/yamlpackagescanner.cpp23
-rw-r--r--src/application-lib/yamlpackagescanner.h2
-rw-r--r--src/common-lib/common-lib.pro5
-rw-r--r--src/common-lib/configcache.cpp419
-rw-r--r--src/common-lib/configcache.h158
-rw-r--r--src/common-lib/configcache_p.h84
-rw-r--r--src/common-lib/logging.cpp6
-rw-r--r--src/common-lib/logging.h2
-rw-r--r--src/common-lib/qtyaml.cpp37
-rw-r--r--src/common-lib/qtyaml.h15
-rw-r--r--src/common-lib/utilities.cpp61
-rw-r--r--src/common-lib/utilities.h8
-rw-r--r--src/intent-client-lib/intentclient.h6
-rw-r--r--src/intent-server-lib/intentserver.h8
-rw-r--r--src/main-lib/configuration.cpp1236
-rw-r--r--src/main-lib/configuration.h103
-rw-r--r--src/main-lib/configuration_p.h171
-rw-r--r--src/main-lib/defaultconfiguration.cpp533
-rw-r--r--src/main-lib/defaultconfiguration.h92
-rw-r--r--src/main-lib/main-lib.pro5
-rw-r--r--src/main-lib/main.cpp28
-rw-r--r--src/main-lib/main.h8
-rw-r--r--src/manager-lib/intentaminterface.cpp32
-rw-r--r--src/manager-lib/intentaminterface.h4
-rw-r--r--src/package-lib/packageextractor.cpp3
-rw-r--r--src/tools/appman/appman.cpp4
-rw-r--r--tests/applicationinstaller/tst_applicationinstaller.cpp4
-rw-r--r--tests/main/tst_main.cpp7
-rw-r--r--tests/packager-tool/tst_packager-tool.cpp17
-rw-r--r--tests/qml/configs/tst_configs.qml2
-rw-r--r--tests/qml/intents/am-config.yaml2
-rw-r--r--tests/yaml/data/cache1.yaml13
-rw-r--r--tests/yaml/data/cache2.yaml14
-rw-r--r--tests/yaml/tst_yaml.cpp145
-rw-r--r--tests/yaml/yaml.pro5
-rw-r--r--util/bash/appman-prompt6
52 files changed, 2385 insertions, 1113 deletions
diff --git a/benchmarks/appman-bench/run.sh b/benchmarks/appman-bench/run.sh
index 2cb1ddd9..e79846bd 100755
--- a/benchmarks/appman-bench/run.sh
+++ b/benchmarks/appman-bench/run.sh
@@ -1,6 +1,7 @@
#!/bin/bash
#############################################################################
##
+## Copyright (C) 2019 The Qt Company Ltd.
## Copyright (C) 2019 Luxoft Sweden AB
## Copyright (C) 2018 Pelagicore AG
## Contact: https://www.qt.io/licensing/
@@ -117,7 +118,7 @@ run_test()
echo "Running $test_qml in $temp_folder"
cp $test_qml $temp_folder/test.qml
- (cd $temp_folder && $APPMAN -c am-config.yaml -r --no-dlt-logging)
+ (cd $temp_folder && $APPMAN -c am-config.yaml --clear-cache --no-dlt-logging)
}
if [ -n "$TEST" ]
diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc
index 972d866a..20a6db30 100644
--- a/doc/configuration.qdoc
+++ b/doc/configuration.qdoc
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -102,7 +103,7 @@ or across multiple config files, the final value is resolved based on these rule
\li bool
\li Prints the build configuration of the application manager in YAML format and exits.
\row
- \li \b --config-file or \b{\c -c}
+ \li \b --config-file or \b -c
\li array<string>
\li Loads configuration settings from a set of files. You can use more than one config
file, for example, to split the configuration cleanly, into a device specific and a
@@ -122,17 +123,24 @@ or across multiple config files, the final value is resolved based on these rule
(default: \c /opt/am/config.yaml)
\row
- \li \b --no-config-cache
+ \li \b --no-cache
\li bool
- \li Disables the caching functionality for the configuration files: the cache is neither
- read from or written to.
+ \li Disables the caching functionality for the configuration files and the application
+ database : the caches are neither read from or written to.
+ (default: false)
\row
- \li \b --clear-config-cache
+ \li \b --clear-cache
\li bool
- \li Although the application manager should detect if the configuration file cache is out
- of sync, you can force-clear the cache on startup with this option.
+ \li Although the application manager should detect if the configuration file and application
+ database caches are out of sync, you can force-clear the caches on startup with this
+ option.
+
+ The old options \c --clear-config-cache, \c -r, and \c --recreate-database are also still
+ supported and do also - despite their names - clear both caches.
+
+ (default: false)
\row
- \li \b --option or \b{\c -o}
+ \li \b --option or \b -o
\li YAML
\li Use this option to set or overwrite parts of your config files from the command line.
This option can be specified multiple times and its values are evaluated the same way
@@ -143,15 +151,15 @@ or across multiple config files, the final value is resolved based on these rule
\li \b --database
\br [\c applications/database]
\li string
- \li To decrease the startup time of the System UI, its application database can be cached
- in a file. This way, in subsequent startups, the System UI doesn't have to scan and
- parse the \c info.yaml files for the applications installed, all over again. This
- option specifies the path for this cache file. (default: empty/disabled)
+ \li Deprecated and ignored. The application database will transparently be cached - see also
+ the \c --clear-cache and \c --no-cache options.
\row
- \li \b --recreate-database or \b{\c -r}
+ \li \b --recreate-database or \b -r
\li bool
- \li Ignores any pre-existing database cache and creates a new one by (re)scanning all
- \c info.yaml files in \c builtin-apps-manifest-dir and \c installed-apps-manifest-dir.
+ \li Deprecated. These options were necessary for the application-manager to react on changes
+ to the application manifest files, but this is not needed anymore. For backward
+ compatibility these options do map to the \c --clear-cache option, which will clear both
+ the configuration and application database cache.
(default: false)
\row
\li \b --builtin-apps-manifest-dir
diff --git a/doc/installation.qdoc b/doc/installation.qdoc
index cccbf07f..d6ba3be2 100644
--- a/doc/installation.qdoc
+++ b/doc/installation.qdoc
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -264,10 +265,10 @@ With everything in place, you can start the application manager:
\badcode
cd /path/to/system-ui
-appman -c /opt/am/config.yaml -c am-config.yaml -r --verbose main.qml
+appman -c /opt/am/config.yaml -c am-config.yaml --verbose main.qml
\endcode
-\c{-r} makes sure to recreate the apps database and \c{--verbose} gives you verbose output,
-which is quite helpful when first setting up the environment.
+\c{--verbose} gives you verbose output, which is quite helpful when first setting up the environment
+and also for debugging purposes.
*/
diff --git a/doc/write-applications.qdoc b/doc/write-applications.qdoc
index 8c0c590a..e492c4c4 100644
--- a/doc/write-applications.qdoc
+++ b/doc/write-applications.qdoc
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -38,8 +39,7 @@ stand-alone QML application, except for these three additional tasks:
\li If you write a QML application, make your QML scene's root element an
ApplicationManagerWindow; or derive your own custom root item from it.
\li Provide a valid \l{Manifest definition}{info.yaml} file.
- \li Make the application manager aware of your application by running
- \c{appman --recreate-database}.
+ \li Restart the application manager to make it aware of your application.
\endlist
\section2 The Root Element
diff --git a/examples/applicationmanager/animated-windows/animated-windows.pro b/examples/applicationmanager/animated-windows/animated-windows.pro
index 539906b7..0e929759 100644
--- a/examples/applicationmanager/animated-windows/animated-windows.pro
+++ b/examples/applicationmanager/animated-windows/animated-windows.pro
@@ -22,7 +22,7 @@ AM_COPY_FILES += am-config.yaml
prefix_build:tpath = $$target.path
else:tpath = $$_PRO_FILE_PWD_
-AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose -r
+AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose
example_sources.path = $$target.path
example_sources.files = $$AM_COPY_FILES $$AM_COPY_DIRECTORIES
diff --git a/examples/applicationmanager/custom-appman/custom-appman.cpp b/examples/applicationmanager/custom-appman/custom-appman.cpp
index 45591130..c02e91c0 100644
--- a/examples/applicationmanager/custom-appman/custom-appman.cpp
+++ b/examples/applicationmanager/custom-appman/custom-appman.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -54,7 +55,7 @@
#include <QtAppManCommon/global.h>
#include <QtAppManCommon/logging.h>
#include <QtAppManMain/main.h>
-#include <QtAppManMain/defaultconfiguration.h>
+#include <QtAppManMain/configuration.h>
#include <QtAppManPackage/packageutilities.h>
#include <QtAppManManager/sudo.h>
@@ -73,7 +74,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
try {
Main a(argc, argv);
- DefaultConfiguration cfg;
+ Configuration cfg;
cfg.parse();
a.setup(&cfg);
diff --git a/examples/applicationmanager/frame-timer/frame-timer.pro b/examples/applicationmanager/frame-timer/frame-timer.pro
index db6258ef..f3870d03 100644
--- a/examples/applicationmanager/frame-timer/frame-timer.pro
+++ b/examples/applicationmanager/frame-timer/frame-timer.pro
@@ -22,7 +22,7 @@ AM_COPY_FILES += am-config.yaml
prefix_build:tpath = $$target.path
else:tpath = $$_PRO_FILE_PWD_
-AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose -r
+AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose
example_sources.path = $$target.path
example_sources.files = $$AM_COPY_FILES $$AM_COPY_DIRECTORIES
diff --git a/examples/applicationmanager/minidesk/doc/src/minidesk.qdoc b/examples/applicationmanager/minidesk/doc/src/minidesk.qdoc
index 867448e8..ea1e7be0 100644
--- a/examples/applicationmanager/minidesk/doc/src/minidesk.qdoc
+++ b/examples/applicationmanager/minidesk/doc/src/minidesk.qdoc
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -66,7 +67,7 @@ generic Inter-Process Communication (IPC).
To start the example, navigate to the \c minidesk folder, and run the following command:
\badcode
-<path-to-appman-binary> -c am-config.yaml -r
+<path-to-appman-binary> -c am-config.yaml
\endcode
The \c appman binary (executable file) is usually located in the Qt installation \c bin folder.
diff --git a/examples/applicationmanager/multi-views/multi-views.pro b/examples/applicationmanager/multi-views/multi-views.pro
index e7e041b9..cdb52da8 100644
--- a/examples/applicationmanager/multi-views/multi-views.pro
+++ b/examples/applicationmanager/multi-views/multi-views.pro
@@ -19,7 +19,7 @@ AM_COPY_FILES += am-config.yaml
prefix_build:tpath = $$target.path
else:tpath = $$_PRO_FILE_PWD_
-AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose -r
+AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose
example_sources.path = $$target.path
example_sources.files = $$AM_COPY_FILES $$AM_COPY_DIRECTORIES
diff --git a/examples/applicationmanager/process-status/process-status.pro b/examples/applicationmanager/process-status/process-status.pro
index ac1576c6..3c96e909 100644
--- a/examples/applicationmanager/process-status/process-status.pro
+++ b/examples/applicationmanager/process-status/process-status.pro
@@ -25,7 +25,7 @@ AM_COPY_FILES += am-config.yaml
prefix_build:tpath = $$target.path
else:tpath = $$_PRO_FILE_PWD_
-AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose -r
+AM_DEFAULT_ARGS = -c $$tpath/am-config.yaml --verbose
example_sources.path = $$target.path
example_sources.files = $$AM_COPY_FILES $$AM_COPY_DIRECTORIES
diff --git a/qmake-features/am-qml-testcase.prf b/qmake-features/am-qml-testcase.prf
index 6958f1bc..fd5d9410 100644
--- a/qmake-features/am-qml-testcase.prf
+++ b/qmake-features/am-qml-testcase.prf
@@ -40,7 +40,7 @@ prefix_build {
}
# The check command
-COMMAND += $$AM_TESTRUNNER_DIR/appman-qmltestrunner -r --no-dlt-logging
+COMMAND += $$AM_TESTRUNNER_DIR/appman-qmltestrunner --no-cache --no-dlt-logging
mac: COMMAND += --dbus=none
!multi-process: {
!build_pass:!qmltest-mode-auto:message("am-qml-testcase: forcing MODE to single-process, because application-manager is built in single-process mode")
diff --git a/src/application-lib/installationreport.cpp b/src/application-lib/installationreport.cpp
index 04335059..cf236dd7 100644
--- a/src/application-lib/installationreport.cpp
+++ b/src/application-lib/installationreport.cpp
@@ -173,7 +173,7 @@ void InstallationReport::deserialize(QIODevice *from)
m_files.clear();
auto docs = YamlParser::parseAllDocuments(from->readAll());
- checkYamlFormat(docs, 3 /*number of expected docs*/, { "am-installation-report" }, 3 /*version*/);
+ checkYamlFormat(docs, 3 /*number of expected docs*/, { { qSL("am-installation-report"), 3 } });
const QVariantMap &root = docs.at(1).toMap();
diff --git a/src/application-lib/packagedatabase.cpp b/src/application-lib/packagedatabase.cpp
index 4c927569..de12f572 100644
--- a/src/application-lib/packagedatabase.cpp
+++ b/src/application-lib/packagedatabase.cpp
@@ -43,6 +43,7 @@
#include <QDir>
#include <QFile>
#include <QScopedPointer>
+#include <QDataStream>
#include "packagedatabase.h"
#include "packageinfo.h"
@@ -51,9 +52,34 @@
#include "installationreport.h"
#include "exception.h"
#include "logging.h"
+#include "configcache.h"
QT_BEGIN_NAMESPACE_AM
+// the templated adaptor class needed to instantiate ConfigCache<PackageInfo> in parse() below
+template<> class ConfigCacheAdaptor<PackageInfo>
+{
+public:
+ PackageInfo *loadFromSource(QIODevice *source, const QString &fileName)
+ {
+ return YamlPackageScanner().scan(source, fileName);
+ }
+ PackageInfo *loadFromCache(QDataStream &ds)
+ {
+ return PackageInfo::readFromDataStream(ds);
+ }
+ void saveToCache(QDataStream &ds, const PackageInfo *pi)
+ {
+ pi->writeToDataStream(ds);
+ }
+
+ void preProcessSourceContent(QByteArray &, const QString &) { }
+ void merge(PackageInfo *, const PackageInfo *) { }
+
+ QStringList *warnings;
+};
+
+
PackageDatabase::PackageDatabase(const QStringList &builtInPackagesDirs,
const QString &installedPackagesDir)
: m_builtInPackagesDirs(builtInPackagesDirs)
@@ -91,17 +117,6 @@ void PackageDatabase::enableSaveToCache()
m_saveToCache = true;
}
-bool PackageDatabase::loadFromCache()
-{
- return false;
- //TODO: read cache file
-}
-
-void PackageDatabase::saveToCache()
-{
- //TODO: write cache file
-}
-
bool PackageDatabase::builtInHasRemovableUpdate(PackageInfo *packageInfo) const
{
if (!packageInfo || packageInfo->isBuiltIn() || !m_installedPackages.contains(packageInfo))
@@ -113,10 +128,9 @@ bool PackageDatabase::builtInHasRemovableUpdate(PackageInfo *packageInfo) const
return false;
}
-
-QVector<PackageInfo *> PackageDatabase::loadManifestsFromDir(const QString &manifestDir, bool scanningBuiltInApps)
+QStringList PackageDatabase::findManifestsInDir(const QDir &manifestDir, bool scanningBuiltInApps)
{
- QVector<PackageInfo *> result;
+ QStringList files;
auto flags = scanningBuiltInApps ? QDir::Dirs | QDir::NoDotAndDotDot
: QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks;
@@ -137,7 +151,7 @@ QVector<PackageInfo *> PackageDatabase::loadManifestsFromDir(const QString &mani
// ignore directory names with weird/forbidden characters
QString pkgIdError;
if (!PackageInfo::isValidApplicationId(pkgDirName, &pkgIdError))
- throw Exception("directory name is not a valid package-id: %1").arg(pkgIdError);
+ throw Exception("not a valid package-id: %1").arg(pkgIdError);
if (!pkgDir.exists(qSL("info.yaml")))
throw Exception("couldn't find an info.yaml manifest");
@@ -145,35 +159,13 @@ QVector<PackageInfo *> PackageDatabase::loadManifestsFromDir(const QString &mani
throw Exception("found a non-built-in package without an installation report");
QString manifestPath = pkgDir.absoluteFilePath(qSL("info.yaml"));
- QScopedPointer<PackageInfo> pkg(PackageInfo::fromManifest(manifestPath));
-
- if (pkg->id() != pkgDir.dirName()) {
- throw Exception("an info.yaml must be in a directory that has"
- " the same name as the package's id: found '%1'").arg(pkg->id());
- }
- if (scanningBuiltInApps) {
- pkg->setBuiltIn(true);
- } else { // 3rd-party apps
- QFile f(pkgDir.absoluteFilePath(qSL(".installation-report.yaml")));
- if (!f.open(QFile::ReadOnly))
- throw Exception(f, "failed to open the installation report");
-
- QScopedPointer<InstallationReport> report(new InstallationReport(pkg->id()));
- try {
- report->deserialize(&f);
- } catch (const Exception &e) {
- throw Exception("Failed to deserialize the installation report %1: %2")
- .arg(f.fileName()).arg(e.errorString());
- }
+ files << manifestPath;
- pkg->setInstallationReport(report.take());
- }
- result.append(pkg.take());
} catch (const Exception &e) {
qCDebug(LogSystem) << "Ignoring package" << pkgDirName << ":" << e.what();
}
}
- return result;
+ return files;
}
void PackageDatabase::parse()
@@ -182,11 +174,6 @@ void PackageDatabase::parse()
throw Exception("PackageDatabase::parse() has been called multiple times");
m_parsed = true;
- if (m_loadFromCache) {
- if (loadFromCache())
- return;
- }
-
if (!m_singlePackagePath.isEmpty()) {
try {
m_builtInPackages.append(PackageInfo::fromManifest(m_singlePackagePath));
@@ -194,15 +181,56 @@ void PackageDatabase::parse()
throw Exception("Failed to load manifest for package: %1").arg(e.errorString());
}
} else {
- for (const QString &dir : m_builtInPackagesDirs)
- m_builtInPackages.append(loadManifestsFromDir(dir, true));
+ QStringList manifestFiles;
+ // parallelize this
+ for (const QString &dir : m_builtInPackagesDirs)
+ manifestFiles << findManifestsInDir(dir, true);
+ int installedOffset = manifestFiles.size();
if (!m_installedPackagesDir.isEmpty())
- m_installedPackages = loadManifestsFromDir(m_installedPackagesDir, false);
- }
+ manifestFiles << findManifestsInDir(m_installedPackagesDir, false);
+
+ AbstractConfigCache::Options cacheOptions = AbstractConfigCache::None;
+ if (!m_loadFromCache)
+ cacheOptions |= AbstractConfigCache::ClearCache;
+ if (!m_loadFromCache && !m_saveToCache)
+ cacheOptions |= AbstractConfigCache::NoCache;
+
+ ConfigCache<PackageInfo> cache(manifestFiles, qSL("appdb"), cacheOptions);
+ cache.parse();
+
+ for (int i = 0; i < manifestFiles.size(); ++i) {
+ bool isBuiltIn = (i < installedOffset);
+ QString manifestFile = manifestFiles.at(i);
+ QDir pkgDir = QFileInfo(manifestFile).dir();
+ QScopedPointer<PackageInfo>pkg(cache.takeResult(i));
+
+ if (pkg->id() != pkgDir.dirName()) {
+ throw Exception("an info.yaml for built-in packages must be in a directory that has"
+ " the same name as the package's id: found '%1'").arg(pkg->id());
+ }
+ if (isBuiltIn) {
+ pkg->setBuiltIn(true);
+ m_builtInPackages.append(pkg.take());
+ } else { // 3rd-party apps
+ QFile f(pkgDir.absoluteFilePath(qSL(".installation-report.yaml")));
+ if (!f.open(QFile::ReadOnly))
+ throw Exception(f, "failed to open the installation report");
+
+ QScopedPointer<InstallationReport> report(new InstallationReport(pkg->id()));
+ try {
+ report->deserialize(&f);
+ } catch (const Exception &e) {
+ throw Exception("Failed to deserialize the installation report %1: %2")
+ .arg(f.fileName()).arg(e.errorString());
+ }
- if (m_saveToCache)
- saveToCache();
+ pkg->setInstallationReport(report.take());
+ pkg->setBaseDir(pkgDir.path());
+ m_installedPackages.append(pkg.take());
+ }
+ }
+ }
}
void PackageDatabase::addPackageInfo(PackageInfo *package)
diff --git a/src/application-lib/packagedatabase.h b/src/application-lib/packagedatabase.h
index 7b6b3584..e1bbb52a 100644
--- a/src/application-lib/packagedatabase.h
+++ b/src/application-lib/packagedatabase.h
@@ -77,10 +77,8 @@ public:
private:
Q_DISABLE_COPY(PackageDatabase)
- QVector<PackageInfo *> loadManifestsFromDir(const QString &manifestDir, bool scanningBuiltInApps);
-
- bool loadFromCache();
- void saveToCache();
+ bool builtInHasRemovableUpdate(PackageInfo *packageInfo) const;
+ QStringList findManifestsInDir(const QDir &manifestDir, bool scanningBuiltInApps);
bool m_loadFromCache = false;
bool m_saveToCache = false;
@@ -92,7 +90,6 @@ private:
QVector<PackageInfo *> m_builtInPackages;
QVector<PackageInfo *> m_installedPackages;
- bool builtInHasRemovableUpdate(PackageInfo *packageInfo) const;
};
QT_END_NAMESPACE_AM
diff --git a/src/application-lib/packageinfo.cpp b/src/application-lib/packageinfo.cpp
index 32ac528a..dc6813d2 100644
--- a/src/application-lib/packageinfo.cpp
+++ b/src/application-lib/packageinfo.cpp
@@ -205,7 +205,7 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds)
QScopedPointer<PackageInfo> pkg(new PackageInfo);
QString baseDir;
- QByteArray installationReport;
+ QByteArray serializedReport;
ds >> pkg->m_id
>> pkg->m_names
@@ -217,12 +217,12 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds)
>> pkg->m_uid
>> pkg->m_dltConfiguration
>> baseDir
- >> installationReport;
+ >> serializedReport;
pkg->m_baseDir.setPath(baseDir);
- if (!installationReport.isEmpty()) {
- QBuffer buffer(&installationReport);
+ if (!serializedReport.isEmpty()) {
+ QBuffer buffer(&serializedReport);
buffer.open(QBuffer::ReadOnly);
pkg->m_installationReport.reset(new InstallationReport(pkg->id()));
try {
@@ -232,6 +232,16 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds)
}
}
+ int applicationsSize;
+ ds >> applicationsSize;
+ while (--applicationsSize >= 0)
+ pkg->m_applications << ApplicationInfo::readFromDataStream(pkg.data(), ds);
+
+ int intentsSize;
+ ds >> intentsSize;
+ while (--intentsSize >= 0)
+ pkg->m_intents << IntentInfo::readFromDataStream(pkg.data(), ds);
+
return pkg.take();
}
diff --git a/src/application-lib/packagescanner.h b/src/application-lib/packagescanner.h
index 9e5956d9..43d921bc 100644
--- a/src/application-lib/packagescanner.h
+++ b/src/application-lib/packagescanner.h
@@ -45,6 +45,8 @@
#include <QtAppManCommon/global.h>
+QT_FORWARD_DECLARE_CLASS(QIODevice)
+
QT_BEGIN_NAMESPACE_AM
class PackageInfo;
@@ -54,7 +56,8 @@ class PackageScanner
public:
virtual ~PackageScanner() = default;
- virtual PackageInfo *scan(const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false) = 0;
+ virtual PackageInfo *scan(const QString &fileName) Q_DECL_NOEXCEPT_EXPR(false) = 0;
+ virtual PackageInfo *scan(QIODevice *source, const QString &fileName) Q_DECL_NOEXCEPT_EXPR(false) = 0;
protected:
PackageScanner() = default;
diff --git a/src/application-lib/yamlpackagescanner.cpp b/src/application-lib/yamlpackagescanner.cpp
index f5ba45ec..4cb6ea4e 100644
--- a/src/application-lib/yamlpackagescanner.cpp
+++ b/src/application-lib/yamlpackagescanner.cpp
@@ -64,14 +64,18 @@ QT_BEGIN_NAMESPACE_AM
YamlPackageScanner::YamlPackageScanner()
{ }
-PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false)
+PackageInfo *YamlPackageScanner::scan(const QString &fileName) Q_DECL_NOEXCEPT_EXPR(false)
{
- try {
- QFile f(filePath);
- if (!f.open(QIODevice::ReadOnly))
- throw Exception(f, "could not open file for reading");
+ QFile f(fileName);
+ if (!f.open(QIODevice::ReadOnly))
+ throw Exception(f, "Cannot open for reading");
+ return scan(&f, f.fileName());
+}
- YamlParser p(f.readAll());
+PackageInfo *YamlPackageScanner::scan(QIODevice *source, const QString &fileName) Q_DECL_NOEXCEPT_EXPR(false)
+{
+ try {
+ YamlParser p(source->readAll());
bool legacy = false;
try {
@@ -87,8 +91,8 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E
QStringList appIds; // duplicate check
QScopedPointer<PackageInfo> pkgInfo(new PackageInfo);
- {
- QFileInfo fi(f);
+ if (!fileName.isEmpty()) {
+ QFileInfo fi(fileName);
pkgInfo->m_baseDir = fi.absoluteDir();
pkgInfo->m_manifestName = fi.fileName();
}
@@ -346,7 +350,8 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E
pkgInfo->validate();
return pkgInfo.take();
} catch (const Exception &e) {
- throw Exception(e.errorCode(), "Failed to parse manifest file %1: %2").arg(QDir().relativeFilePath(filePath), e.errorString());
+ throw Exception(e.errorCode(), "Failed to parse manifest file %1: %2")
+ .arg(!fileName.isEmpty() ? QDir().relativeFilePath(fileName) : qSL("<stream>"), e.errorString());
}
}
diff --git a/src/application-lib/yamlpackagescanner.h b/src/application-lib/yamlpackagescanner.h
index cbd8cfcd..67b8ef91 100644
--- a/src/application-lib/yamlpackagescanner.h
+++ b/src/application-lib/yamlpackagescanner.h
@@ -43,6 +43,7 @@
#pragma once
+#include <QIODevice>
#include <QtAppManApplication/packagescanner.h>
QT_BEGIN_NAMESPACE_AM
@@ -54,6 +55,7 @@ public:
YamlPackageScanner();
PackageInfo *scan(const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false) override;
+ PackageInfo *scan(QIODevice *source, const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false) override;
};
QT_END_NAMESPACE_AM
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..c23bbccc
--- /dev/null
+++ b/src/common-lib/configcache.cpp
@@ -0,0 +1,419 @@
+/****************************************************************************
+**
+** 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();
+
+ // optimization, so that we can quickly determine if we have a complete match, even if the order
+ // of the input files changed since the cache was last created
+ if (!(d->options & MergedResult))
+ rawFilePaths.sort();
+
+ // 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.\n")
+ .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/logging.cpp b/src/common-lib/logging.cpp
index 09a2b55f..4845b82a 100644
--- a/src/common-lib/logging.cpp
+++ b/src/common-lib/logging.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -125,6 +126,10 @@ static const char *s_defaultSystemUiDltDescription = "Qt 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
@@ -144,6 +149,7 @@ 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)
diff --git a/src/common-lib/logging.h b/src/common-lib/logging.h
index 51b64909..cd73a428 100644
--- a/src/common-lib/logging.h
+++ b/src/common-lib/logging.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -57,6 +58,7 @@ 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/qtyaml.cpp b/src/common-lib/qtyaml.cpp
index 512a3006..1a5aa041 100644
--- a/src/common-lib/qtyaml.cpp
+++ b/src/common-lib/qtyaml.cpp
@@ -46,6 +46,8 @@
#include <QDebug>
#include <QtNumeric>
#include <QTextCodec>
+#include <QFileInfo>
+#include <QDir>
#include <yaml.h>
@@ -379,6 +381,8 @@ QByteArray yamlFromVariantDocuments(const QVector<QVariant> &documents, YamlStyl
class YamlParserPrivate
{
public:
+ QString sourceName;
+ QString sourceDir;
QByteArray data;
QTextCodec *codec = nullptr;
bool parsedHeader = false;
@@ -388,10 +392,15 @@ public:
};
-YamlParser::YamlParser(const QByteArray &data)
+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));
@@ -419,6 +428,21 @@ YamlParser::~YamlParser()
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);
@@ -474,6 +498,8 @@ void YamlParser::nextEvent()
do {
if (!yaml_parser_parse(&d->parser, &d->event))
throw YamlParserException(this, "cannot get next event");
+ if (d->event.type == YAML_ALIAS_EVENT)
+ throw YamlParserException(this, "anchors and aliases are not supported");
} while (d->event.type == YAML_NO_EVENT);
}
@@ -519,7 +545,7 @@ QVariant YamlParser::parseScalar() const
};
static const QVariant staticValues[] = {
- QVariant(), // ValueNull
+ QVariant::fromValue(nullptr), // ValueNull
QVariant(true), // ValueTrue
QVariant(false), // ValueFalse
QVariant(qQNaN()), // ValueNaN
@@ -773,7 +799,8 @@ void YamlParser::parseFields(const std::vector<Field> &fields)
allowedEvents.append(YAML_SEQUENCE_START_EVENT);
if (!allowedEvents.contains(d->event.type)) { // ALIASES MISSING HERE!
- throw YamlParserException(this, "Field '%1' expected to have one of these types (%2), but got %3")
+ //TODO: better output -- the integer here is confusing
+ throw YamlParserException(this, "Field '%1' expected to have one of these types [%2], but got %3")
.arg(field->name).arg(allowedEvents).arg(d->event.type);
}
@@ -806,11 +833,11 @@ YamlParserException::YamlParserException(YamlParser *p, const char *errorString)
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));
- int lpos = context.lastIndexOf(qL1C('\n'), int(mark.index));
context = context.mid(lpos + 1, rpos == -1 ? context.size() : rpos - lpos - 1);
- m_errorString.append(qSL(" at line %1, column %2 [%3]").arg(mark.line + 1).arg(mark.column + 1).arg(context));
+ m_errorString.append(qSL(" at line %1, column %2 [.. %3 ..]").arg(mark.line + 1).arg(mark.column + 1).arg(context));
if (isProblem)
m_errorString.append(qSL(" (%1)").arg(QString::fromUtf8(p->d->parser.problem)));
if (errorString)
diff --git a/src/common-lib/qtyaml.h b/src/common-lib/qtyaml.h
index facb30d1..33d51ec9 100644
--- a/src/common-lib/qtyaml.h
+++ b/src/common-lib/qtyaml.h
@@ -51,9 +51,8 @@
#include <QByteArray>
#include <QString>
#include <QVariant>
-
-#include "global.h"
-#include "exception.h"
+#include <QtAppManCommon/global.h>
+#include <QtAppManCommon/exception.h>
QT_BEGIN_NAMESPACE_AM
@@ -100,9 +99,13 @@ class YamlParserException;
class YamlParser
{
public:
- YamlParser(const QByteArray &data);
+ 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();
@@ -134,7 +137,8 @@ public:
FieldTypes types;
std::function<void(YamlParser *)> callback;
- Field(const char *_name, bool _required, FieldTypes _types, const 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)
@@ -163,3 +167,4 @@ public:
};
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/utilities.cpp b/src/common-lib/utilities.cpp
index 392c78dd..c70d6abb 100644
--- a/src/common-lib/utilities.cpp
+++ b/src/common-lib/utilities.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -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) {
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 86d5b6ba..dd07913c 100644
--- a/src/common-lib/utilities.h
+++ b/src/common-lib/utilities.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -43,6 +44,7 @@
#pragma once
#include <QVector>
+#include <QPair>
#include <QByteArray>
#include <QMultiMap>
#include <QVariant>
@@ -62,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
diff --git a/src/intent-client-lib/intentclient.h b/src/intent-client-lib/intentclient.h
index 2ecccb31..f3ba44c2 100644
--- a/src/intent-client-lib/intentclient.h
+++ b/src/intent-client-lib/intentclient.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -102,8 +103,9 @@ private:
QList<IntentClientRequest *> m_waiting;
QMap<QPair<QString, QString>, IntentHandler *> m_handlers; // intentId + appId -> handler
- int m_replyFromSystemTimeout = 20000;
- int m_replyFromApplicationTimeout = 4000;
+ // no timeouts by default -- these have to be set at runtime
+ int m_replyFromSystemTimeout = 0;
+ int m_replyFromApplicationTimeout = 0;
IntentClientSystemInterface *m_systemInterface;
friend class IntentClientSystemInterface;
diff --git a/src/intent-server-lib/intentserver.h b/src/intent-server-lib/intentserver.h
index a2ae8281..6bd16228 100644
--- a/src/intent-server-lib/intentserver.h
+++ b/src/intent-server-lib/intentserver.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -159,9 +160,10 @@ private:
QQueue<IntentServerRequest *> m_startingAppQueue;
QQueue<IntentServerRequest *> m_sentToAppQueue;
- int m_disambiguationTimeout = 10000;
- int m_startingAppTimeout = 3000;
- int m_sentToAppTimeout = 5000;
+ // no timeouts by default -- these have to be set at runtime
+ int m_disambiguationTimeout = 0;
+ int m_startingAppTimeout = 0;
+ int m_sentToAppTimeout = 0;
QVector<Intent *> m_intents;
diff --git a/src/main-lib/configuration.cpp b/src/main-lib/configuration.cpp
index d117fed6..362ec65b 100644
--- a/src/main-lib/configuration.cpp
+++ b/src/main-lib/configuration.cpp
@@ -41,150 +41,172 @@
**
****************************************************************************/
-#include <QFile>
-#include <QFileInfo>
#include <QCoreApplication>
-#include <QDebug>
+#include <QFile>
#include <QStandardPaths>
+#include <QMetaEnum>
+#include <QProcessEnvironment>
+#include <private/qvariant_p.h>
+
#include <QDataStream>
-#include <QCryptographicHash>
-#include <QElapsedTimer>
-#include <QtConcurrent/QtConcurrent>
+#include <QFileInfo>
+#include <QDir>
+#include <QBuffer>
+
+#if !defined(AM_HEADLESS)
+# include <QGuiApplication>
+#endif
#include <functional>
+#if defined(Q_OS_LINUX)
+# include <sys/file.h>
+#endif
+
#include "global.h"
#include "logging.h"
#include "qtyaml.h"
+#include "configcache.h"
#include "utilities.h"
#include "exception.h"
-#include "qml-utilities.h"
+#include "utilities.h"
#include "configuration.h"
-
-// enable this to benchmark the config cache
-//#define AM_TIME_CONFIG_PARSING
-
-// use QtConcurrent to parse the config files, if there are more than x config files
-#define AM_PARALLEL_THRESHOLD 1
+#include "configuration_p.h"
QT_BEGIN_NAMESPACE_AM
-template<> bool Configuration::value(const char *clname, const QVector<const char *> &cfname) const
+template<> bool Configuration::value(const char *clname, const bool &cfvalue) const
{
- return (clname && m_clp.isSet(qL1S(clname))) || findInConfigFile(cfname).toBool();
+ return (clname && m_clp.isSet(qL1S(clname))) || cfvalue;
}
-template<> QString Configuration::value(const char *clname, const QVector<const char *> &cfname) const
+template<> QString Configuration::value(const char *clname, const QString &cfvalue) const
{
- QString clval;
- if (clname)
- clval = m_clp.value(qL1S(clname));
- bool cffound;
- QString cfval = findInConfigFile(cfname, &cffound).toString();
- return ((clname && m_clp.isSet(qL1S(clname))) || !cffound) ? clval : cfval;
+ return (clname && m_clp.isSet(qL1S(clname))) ? m_clp.value(qL1S(clname)) : cfvalue;
}
-template<> QStringList Configuration::value(const char *clname, const QVector<const char *> &cfname) const
+template<> QStringList Configuration::value(const char *clname, const QStringList &cfvalue) const
{
QStringList result;
if (clname)
result = m_clp.values(qL1S(clname));
- if (!cfname.isEmpty())
- result += variantToStringList(findInConfigFile(cfname));
+ if (!cfvalue.isEmpty())
+ result += cfvalue;
return result;
}
-template<> QVariant Configuration::value(const char *clname, const QVector<const char *> &cfname) const
-{
- Q_ASSERT(!clname);
- Q_UNUSED(clname);
- return findInConfigFile(cfname);
-}
-QVariant Configuration::findInConfigFile(const QVector<const char *> &path, bool *found) const
+// the templated adaptor class needed to instantiate ConfigCache<ConfigurationData> in parse() below
+template<> class ConfigCacheAdaptor<ConfigurationData>
{
- if (found)
- *found = false;
-
- if (path.isEmpty())
- return QVariant();
-
- QVariantMap var = m_config;
-
- for (int i = 0; i < (path.size() - 1); ++i) {
- QVariant subvar = var.value(qL1S(path.at(i)));
- if (subvar.type() == QVariant::Map)
- var = subvar.toMap();
- else
- return QVariant();
+public:
+ static ConfigurationData *loadFromSource(QIODevice *source, const QString &fileName)
+ {
+ return ConfigurationData::loadFromSource(source, fileName);
}
- if (found)
- *found = var.contains(qL1S(path.last()));
- return var.value(qL1S(path.last()));
-}
-
-void Configuration::mergeConfig(const QVariantMap &other)
-{
- recursiveMergeVariantMap(m_config, other);
-}
-
-QByteArray Configuration::substituteVars(const QByteArray &str, const QString &fileName,
- QStringList *deploymentWarnings)
-{
- QByteArray string = str;
- int posBeg = -1;
- int posEnd = -1;
- while (true) {
- if ((posBeg = string.indexOf("${", posEnd + 1)) < 0)
- break;
- if ((posEnd = string.indexOf('}', posBeg + 2)) < 0)
- break;
-
- const QByteArray varName = string.mid(posBeg + 2, posEnd - posBeg - 2);
-
- QByteArray varValue;
- if (varName == "CONFIG_PWD") {
- static QByteArray path;
- if (path.isEmpty() && !fileName.isEmpty())
- path = QFileInfo(fileName).path().toUtf8();
- varValue = path;
- } else if (varName.startsWith("env:")) {
- varValue = qgetenv(varName.constData() + 4);
- } else if (varName.startsWith("stdpath:")) {
- bool exists;
- int loc = QMetaEnum::fromType<QStandardPaths::StandardLocation>().keyToValue(varName.constData() + 8, &exists);
- if (exists)
- varValue = QStandardPaths::writableLocation(static_cast<QStandardPaths::StandardLocation>(loc)).toUtf8();
- }
-
- if (varValue.isEmpty() && deploymentWarnings) {
- *deploymentWarnings << qL1S("Could not replace variable ${") + qL1S(varName)
- + qL1S("} while parsing ") + fileName;
- continue;
- }
- string.replace(posBeg, varName.length() + 3, varValue);
- // varName and varValue most likely have a different length, so we have to adjust
- posEnd = posEnd - 3 - varName.length() + varValue.length();
+ void preProcessSourceContent(QByteArray &sourceContent, const QString &fileName)
+ {
+ sourceContent = ConfigurationData::substituteVars(sourceContent, fileName, warnings);
}
- return string;
-}
+ ConfigurationData *loadFromCache(QDataStream &ds)
+ {
+ return ConfigurationData::loadFromCache(ds);
+ }
+ void saveToCache(QDataStream &ds, const ConfigurationData *cd)
+ {
+ cd->saveToCache(ds);
+ }
+ static void merge(ConfigurationData *to, const ConfigurationData *from)
+ {
+ to->mergeFrom(from);
+ }
+ QStringList *warnings;
+};
+
+Configuration::Configuration(const char *additionalDescription,
+ bool onlyOnePositionalArgument)
+ : Configuration(QStringList(), qSL(":/build-config.yaml"),
+ additionalDescription, onlyOnePositionalArgument)
+{ }
-Configuration::Configuration(const QStringList &defaultConfigFilePaths, const QString &buildConfigFilePath)
+Configuration::Configuration(const QStringList &defaultConfigFilePaths,
+ const QString &buildConfigFilePath,
+ const char *additionalDescription,
+ bool onlyOnePositionalArgument)
: m_defaultConfigFilePaths(defaultConfigFilePaths)
, m_buildConfigFilePath(buildConfigFilePath)
+ , m_onlyOnePositionalArgument(onlyOnePositionalArgument)
{
- m_clp.addHelpOption();
- m_clp.addVersionOption();
- QCommandLineOption cf { { qSL("c"), qSL("config-file") }, qSL("load cnfiguration from file (can be given multiple times)."), qSL("files") };
+ // using QStringLiteral for all strings here adds a few KB of ro-data, but will also improve
+ // startup times slightly: less allocations and copies. MSVC cannot cope with multi-line though
+
+ const char *description =
+ "In addition to the commandline options below, the following environment\n"
+ "variables can be set:\n\n"
+ " AM_STARTUP_TIMER If set to 1, a startup performance analysis will be printed\n"
+ " on the console. Anything other than 1 will be interpreted\n"
+ " as the name of a file that is used instead of the console.\n"
+ "\n"
+ " AM_FORCE_COLOR_OUTPUT Can be set to 'on' to force color output to the console\n"
+ " and to 'off' to disable it. Any other value will result\n"
+ " in the default, auto-detection behavior.\n";
+
+ m_clp.setApplicationDescription(qSL("\n") + QCoreApplication::applicationName() + qSL("\n\n")
+ + (additionalDescription ? (qL1S(additionalDescription) + qSL("\n\n")) : QString())
+ + qL1S(description));
+
+ m_clp.addOption({ { qSL("h"), qSL("help")
+#if defined(Q_OS_WINDOWS)
+ , qSL("?")
+#endif
+ }, qSL("Displays this help.") });
+ m_clp.addOption({ qSL("version"), qSL("Displays version information.") });
+ QCommandLineOption cf { { qSL("c"), qSL("config-file") },
+ qSL("Load configuration from file (can be given multiple times)."), qSL("files") };
cf.setDefaultValues(m_defaultConfigFilePaths);
m_clp.addOption(cf);
- m_clp.addOption({ { qSL("o"), qSL("option") }, qSL("override a specific config option."), qSL("yaml-snippet") });
- m_clp.addOption({ qSL("no-config-cache"), qSL("disable the use of the config file cache.") });
- m_clp.addOption({ qSL("clear-config-cache"), qSL("ignore an existing config file cache.") });
+ m_clp.addOption({ { qSL("o"), qSL("option") }, qSL("Override a specific config option."), qSL("yaml-snippet") });
+ m_clp.addOption({ { qSL("no-cache"), qSL("no-config-cache") },
+ qSL("Disable the use of the config and appdb file cache.") });
+ m_clp.addOption({ { qSL("clear-cache"), qSL("clear-config-cache") },
+ qSL("Ignore an existing config and appdb file cache.") });
+ m_clp.addOption({ { qSL("r"), qSL("recreate-database") },
+ qSL("Backwards compatibility: synonyms for --clear-cache.") });
if (!buildConfigFilePath.isEmpty())
- m_clp.addOption({ qSL("build-config"), qSL("dumps the build configuration and exits.") });
+ m_clp.addOption({ qSL("build-config"), qSL("Dumps the build configuration and exits.") });
+
+ m_clp.addPositionalArgument(qSL("qml-file"), qSL("The main QML file."));
+ m_clp.addOption({ qSL("database"), qSL("Deprecated (ingored)."), qSL("file") });
+ m_clp.addOption({ qSL("builtin-apps-manifest-dir"), qSL("Base directory for built-in application manifests."), qSL("dir") });
+ m_clp.addOption({ qSL("installation-dir"), qSL("Base directory for package installations."), qSL("dir") });
+ m_clp.addOption({ qSL("document-dir"), qSL("Base directory for per-package document directories."), qSL("dir") });
+ m_clp.addOption({ qSL("installed-apps-manifest-dir"), qSL("Deprecated (ignored)."), qSL("dir") });
+ m_clp.addOption({ qSL("app-image-mount-dir"), qSL("Deprecated (ignored)."), qSL("dir") });
+ m_clp.addOption({ qSL("disable-installer"), qSL("Disable the application installer sub-system.") });
+ m_clp.addOption({ qSL("disable-intents"), qSL("Disable the intents sub-system.") });
+#if defined(QT_DBUS_LIB)
+ m_clp.addOption({ qSL("dbus"), qSL("Register on the specified D-Bus."), qSL("<bus>|system|session|none|auto"), qSL("auto") });
+ m_clp.addOption({ qSL("start-session-dbus"), qSL("Deprecated (ignored).") });
+#endif
+ m_clp.addOption({ qSL("fullscreen"), qSL("Display in full-screen.") });
+ m_clp.addOption({ qSL("no-fullscreen"), qSL("Do not display in full-screen.") });
+ m_clp.addOption({ qSL("I"), qSL("Additional QML import path."), qSL("dir") });
+ m_clp.addOption({ { qSL("v"), qSL("verbose") }, qSL("Verbose output.") });
+ m_clp.addOption({ qSL("slow-animations"), qSL("Run all animations in slow motion.") });
+ m_clp.addOption({ qSL("load-dummydata"), qSL("Loads QML dummy-data.") });
+ m_clp.addOption({ qSL("no-security"), qSL("Disables all security related checks (dev only!)") });
+ m_clp.addOption({ qSL("development-mode"), qSL("Enable development mode, allowing installation of dev-signed packages.") });
+ m_clp.addOption({ qSL("no-ui-watchdog"), qSL("Disables detecting hung UI applications (e.g. via Wayland's ping/pong).") });
+ m_clp.addOption({ qSL("no-dlt-logging"), qSL("Disables logging using automotive DLT.") });
+ m_clp.addOption({ qSL("force-single-process"), qSL("Forces single-process mode even on a wayland enabled build.") });
+ m_clp.addOption({ qSL("force-multi-process"), qSL("Forces multi-process mode. Will exit immediately if this is not possible.") });
+ m_clp.addOption({ qSL("wayland-socket-name"), qSL("Use this file name to create the wayland socket."), qSL("socket") });
+ m_clp.addOption({ qSL("single-app"), qSL("Runs a single application only (ignores the database)"), qSL("info.yaml file") }); // rename single-package
+ m_clp.addOption({ qSL("logging-rule"), qSL("Adds a standard Qt logging rule."), qSL("rule") });
+ m_clp.addOption({ qSL("qml-debug"), qSL("Enables QML debugging and profiling.") });
+ m_clp.addOption({ qSL("enable-touch-emulation"), qSL("Enables the touch emulation, converting mouse to touch events.") });
}
QVariant Configuration::buildConfig() const
@@ -200,9 +222,7 @@ QVariant Configuration::buildConfig() const
}
Configuration::~Configuration()
-{
-
-}
+{ }
// vvvv copied from QCommandLineParser ... why is this not public API?
@@ -287,193 +307,891 @@ void Configuration::parseWithArguments(const QStringList &arguments, QStringList
QStringList configFilePaths = m_clp.values(qSL("config-file"));
- struct ConfigFile
- {
- QString filePath; // abs. file path
- QByteArray checksum; // sha1 (fast and sufficient for this use-case)
- QByteArray content;
- QVariantMap config;
- };
- QVarLengthArray<ConfigFile> configFiles(configFilePaths.size());
+ AbstractConfigCache::Options cacheOptions = AbstractConfigCache::MergedResult;
+ if (noCache())
+ cacheOptions |= AbstractConfigCache::NoCache;
+ if (clearCache())
+ cacheOptions |= AbstractConfigCache::ClearCache;
- for (int i = 0; i < configFiles.size(); ++i)
- configFiles[i].filePath = QFileInfo(configFilePaths.at(i)).absoluteFilePath();
+ if (configFilePaths.isEmpty()) {
+ m_data = new ConfigurationData();
+ } else {
+ ConfigCache<ConfigurationData> cache(configFilePaths, qSL("config"), cacheOptions);
- bool noConfigCache = m_clp.isSet(qSL("no-config-cache"));
- bool clearConfigCache = m_clp.isSet(qSL("clear-config-cache"));
+ try {
+ cache.parse(deploymentWarnings);
+ m_data = cache.takeMergedResult();
+ } catch (const Exception &e) {
+ showParserMessage(e.errorString(), ErrorMessage);
+ exit(1);
+ }
+ }
- const QDir cacheLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+ const QStringList options = m_clp.values(qSL("o"));
+ for (const QString &option : options) {
+ QByteArray yaml("formatVersion: 1\nformatType: am-configuration\n---\n");
+ yaml.append(option.toUtf8());
+ QBuffer buffer(&yaml);
+ buffer.open(QIODevice::ReadOnly);
+ try {
+ ConfigurationData *cd = ConfigCacheAdaptor<ConfigurationData>::loadFromSource(&buffer, qSL("command line"));
+ if (cd)
+ ConfigCacheAdaptor<ConfigurationData>::merge(m_data, cd);
+ } catch (const Exception &e) {
+ showParserMessage(QString::fromLatin1("Could not parse --option value: %1.\n")
+ .arg(e.errorString()),
+ ErrorMessage);
+ exit(1);
+ }
+ }
- if (!cacheLocation.exists())
- cacheLocation.mkpath(qSL("."));
+ // early sanity checks
+ if (m_onlyOnePositionalArgument && (m_clp.positionalArguments().size() > 1)) {
+ showParserMessage(qL1S("Only one main qml file can be specified.\n"), ErrorMessage);
+ exit(1);
+ }
- const QString cacheFilePath = cacheLocation.absoluteFilePath(qSL("appman-config.cache"));
+ if (installationDir().isEmpty()) {
+ const auto ilocs = m_data->installationLocations;
+ if (!ilocs.isEmpty() && deploymentWarnings) {
+ *deploymentWarnings << qL1S("Support for \"installationLocations\" in the main config file has been removed:");
+
+ for (const auto iloc : ilocs) {
+ QVariantMap map = iloc.toMap();
+ QString id = map.value(qSL("id")).toString();
+ if (id == qSL("internal-0")) {
+ m_installationDir = map.value(qSL("installationPath")).toString();
+ m_documentDir = map.value(qSL("documentPath")).toString();
+ if (deploymentWarnings)
+ *deploymentWarnings << qL1S(" * still using installation location \"internal-0\" for backward compatibility");
+ } else if (deploymentWarnings) {
+ *deploymentWarnings << qL1S(" * ignoring installation location ") + id;
+ }
+ }
+ }
+ }
- QFile cacheFile(cacheFilePath);
- QAtomicInt useCache = false;
- QVariantMap cache;
+ if (installationDir().isEmpty() && deploymentWarnings) {
+ *deploymentWarnings << qL1S("No --installation-dir command line parameter or"
+ " applications/installationDir configuration key specified. It won't be possible to install,"
+ " remove or access installable packages.");
+ }
- static const quint32 CacheMagicHeader = 0xe42ad845;
+ if (value<bool>("start-session-dbus") && deploymentWarnings)
+ *deploymentWarnings << qL1S("Option \"--start-session-dbus\" has been deprecated and will be ignored.");
+}
- if (!noConfigCache && !clearConfigCache) {
- if (cacheFile.open(QFile::ReadOnly)) {
- try {
- QDataStream ds(&cacheFile);
- quint32 magic;
- ds >> magic;
+ConfigurationData *ConfigurationData::loadFromCache(QDataStream &ds)
+{
+ // IMPORTANT: when doing changes to ConfigurationData, remember to adjust all of
+ // loadFromCache(), saveToCache() and mergeFrom() at the same time!
+
+ ConfigurationData *cd = new ConfigurationData;
+ ds >> cd->runtimes.configurations
+ >> cd->containers.configurations
+ >> cd->containers.selection
+ >> cd->intents.disable
+ >> cd->intents.timeouts.disambiguation
+ >> cd->intents.timeouts.startApplication
+ >> cd->intents.timeouts.replyFromApplication
+ >> cd->intents.timeouts.replyFromSystem
+ >> cd->plugins.startup
+ >> cd->plugins.container
+ >> cd->logging.dlt.id
+ >> cd->logging.dlt.description
+ >> cd->logging.rules
+ >> cd->logging.messagePattern
+ >> cd->logging.useAMConsoleLogger
+ >> cd->installer.disable
+ >> cd->installer.caCertificates
+ >> cd->installer.applicationUserIdSeparation.maxUserId
+ >> cd->installer.applicationUserIdSeparation.minUserId
+ >> cd->installer.applicationUserIdSeparation.commonGroupId
+ >> cd->dbus.policies
+ >> cd->dbus.registrations
+ >> cd->quicklaunch.idleLoad
+ >> cd->quicklaunch.runtimesPerContainer
+ >> cd->ui.style
+ >> cd->ui.mainQml
+ >> cd->ui.resources
+ >> cd->ui.fullscreen
+ >> cd->ui.windowIcon
+ >> cd->ui.importPaths
+ >> cd->ui.pluginPaths
+ >> cd->ui.iconThemeName
+ >> cd->ui.loadDummyData
+ >> cd->ui.enableTouchEmulation
+ >> cd->ui.iconThemeSearchPaths
+ >> cd->ui.opengl
+ >> cd->applications.builtinAppsManifestDir
+ >> cd->applications.installationDir
+ >> cd->applications.documentDir
+ >> cd->installationLocations
+ >> cd->crashAction
+ >> cd->systemProperties
+ >> cd->flags.noSecurity
+ >> cd->flags.noUiWatchdog
+ >> cd->flags.developmentMode
+ >> cd->flags.forceMultiProcess
+ >> cd->flags.forceSingleProcess;
+ return cd;
+}
- if (magic != CacheMagicHeader)
- throw Exception("failed to read config cache header");
+void ConfigurationData::saveToCache(QDataStream &ds) const
+{
+ // IMPORTANT: when doing changes to ConfigurationData, remember to adjust all of
+ // loadFromCache(), saveToCache() and mergeFrom() at the same time!
+
+ ds << runtimes.configurations
+ << containers.configurations
+ << containers.selection
+ << intents.disable
+ << intents.timeouts.disambiguation
+ << intents.timeouts.startApplication
+ << intents.timeouts.replyFromApplication
+ << intents.timeouts.replyFromSystem
+ << plugins.startup
+ << plugins.container
+ << logging.dlt.id
+ << logging.dlt.description
+ << logging.rules
+ << logging.messagePattern
+ << logging.useAMConsoleLogger
+ << installer.disable
+ << installer.caCertificates
+ << installer.applicationUserIdSeparation.maxUserId
+ << installer.applicationUserIdSeparation.minUserId
+ << installer.applicationUserIdSeparation.commonGroupId
+ << dbus.policies
+ << dbus.registrations
+ << quicklaunch.idleLoad
+ << quicklaunch.runtimesPerContainer
+ << ui.style
+ << ui.mainQml
+ << ui.resources
+ << ui.fullscreen
+ << ui.windowIcon
+ << ui.importPaths
+ << ui.pluginPaths
+ << ui.iconThemeName
+ << ui.loadDummyData
+ << ui.enableTouchEmulation
+ << ui.iconThemeSearchPaths
+ << ui.opengl
+ << applications.builtinAppsManifestDir
+ << applications.installationDir
+ << applications.documentDir
+ << installationLocations
+ << crashAction
+ << systemProperties
+ << flags.noSecurity
+ << flags.noUiWatchdog
+ << flags.developmentMode
+ << flags.forceMultiProcess
+ << flags.forceSingleProcess;
+}
- QVector<QPair<QString, QByteArray>> configChecksums; // abs. file path -> sha1
- ds >> configChecksums >> cache;
+// templates would we way nicer, but we cannot get nice pointers-to-member-data for all elements in
+// our sub-structs in a generic way without a lot of boilerplate code
+#define MERGE_SCALAR(x) if (from->x != def.x) { this->x = from->x; } else { }
+#define MERGE_LIST(x) this->x.append(from->x)
+#define MERGE_MAP(x) recursiveMergeVariantMap(this->x, from->x)
- if (ds.status() != QDataStream::Ok)
- throw Exception("failed to read config cache content");
+void ConfigurationData::mergeFrom(const ConfigurationData *from)
+{
+ // IMPORTANT: when doing changes to ConfigurationData, remember to adjust all of
+ // loadFromCache(), saveToCache() and mergeFrom() at the same time!
+
+ static const ConfigurationData def;
+
+ MERGE_MAP(runtimes.configurations);
+ MERGE_MAP(containers.configurations);
+
+ MERGE_SCALAR(intents.disable);
+ MERGE_SCALAR(intents.timeouts.disambiguation);
+ MERGE_SCALAR(intents.timeouts.startApplication);
+ MERGE_SCALAR(intents.timeouts.replyFromApplication);
+ MERGE_SCALAR(intents.timeouts.replyFromSystem);
+ MERGE_LIST(plugins.startup);
+ MERGE_LIST(plugins.container);
+ MERGE_SCALAR(logging.dlt.id);
+ MERGE_SCALAR(logging.dlt.description);
+ MERGE_LIST(logging.rules);
+ MERGE_LIST(logging.messagePattern);
+ MERGE_SCALAR(logging.useAMConsoleLogger);
+ MERGE_SCALAR(installer.disable);
+ MERGE_LIST(installer.caCertificates);
+ MERGE_SCALAR(installer.applicationUserIdSeparation.maxUserId);
+ MERGE_SCALAR(installer.applicationUserIdSeparation.minUserId);
+ MERGE_SCALAR(installer.applicationUserIdSeparation.commonGroupId);
+ MERGE_MAP(dbus.policies);
+ MERGE_MAP(dbus.registrations);
+ MERGE_SCALAR(quicklaunch.idleLoad);
+ MERGE_SCALAR(quicklaunch.runtimesPerContainer);
+ MERGE_SCALAR(ui.style);
+ MERGE_SCALAR(ui.mainQml);
+ MERGE_LIST(ui.resources);
+ MERGE_SCALAR(ui.fullscreen);
+ MERGE_SCALAR(ui.windowIcon);
+ MERGE_LIST(ui.importPaths);
+ MERGE_LIST(ui.pluginPaths);
+ MERGE_SCALAR(ui.iconThemeName);
+ MERGE_SCALAR(ui.loadDummyData);
+ MERGE_SCALAR(ui.enableTouchEmulation);
+ MERGE_LIST(ui.iconThemeSearchPaths);
+ MERGE_MAP(ui.opengl);
+ MERGE_LIST(applications.builtinAppsManifestDir);
+ MERGE_SCALAR(applications.installationDir);
+ MERGE_SCALAR(applications.documentDir);
+ MERGE_LIST(installationLocations);
+ MERGE_MAP(crashAction);
+ MERGE_MAP(systemProperties);
+ MERGE_SCALAR(flags.noSecurity);
+ MERGE_SCALAR(flags.noUiWatchdog);
+ MERGE_SCALAR(flags.developmentMode);
+ MERGE_SCALAR(flags.forceMultiProcess);
+ MERGE_SCALAR(flags.forceSingleProcess);
+}
- if (configFiles.count() != configChecksums.count())
- throw Exception("the number of cached config files does not match the current set");
+QByteArray ConfigurationData::substituteVars(const QByteArray &sourceContent, const QString &fileName,
+ QStringList *deploymentWarnings)
+{
+ QByteArray string = sourceContent;
+ int posBeg = -1;
+ int posEnd = -1;
+ while (true) {
+ if ((posBeg = string.indexOf("${", posEnd + 1)) < 0)
+ break;
+ if ((posEnd = string.indexOf('}', posBeg + 2)) < 0)
+ break;
- for (int i = 0; i < configFiles.count(); ++i) {
- ConfigFile &cf = configFiles[i];
- if (cf.filePath != configChecksums.at(i).first)
- throw Exception("the cached config file names do not match the current set (or their order changed)");
- cf.checksum = configChecksums.at(i).second;
- }
- useCache = true;
+ const QByteArray varName = string.mid(posBeg + 2, posEnd - posBeg - 2);
-#if defined(AM_TIME_CONFIG_PARSING)
- qCDebug(LogSystem) << "Config parsing: cache loaded after" << (timer.nsecsElapsed() / 1000) << "usec";
-#endif
- } catch (const Exception &e) {
- if (deploymentWarnings)
- *deploymentWarnings << qL1S("Failed to read config cache:") + qL1S(e.what());
- }
+ QByteArray varValue;
+ if (varName == "CONFIG_PWD") {
+ static QByteArray path;
+ if (path.isEmpty() && !fileName.isEmpty())
+ path = QFileInfo(fileName).path().toUtf8();
+ varValue = path;
+ } else if (varName.startsWith("env:")) {
+ varValue = qgetenv(varName.constData() + 4);
+ } else if (varName.startsWith("stdpath:")) {
+ bool exists;
+ int loc = QMetaEnum::fromType<QStandardPaths::StandardLocation>().keyToValue(varName.constData() + 8, &exists);
+ if (exists)
+ varValue = QStandardPaths::writableLocation(static_cast<QStandardPaths::StandardLocation>(loc)).toUtf8();
+ }
+
+ if (varValue.isNull() && deploymentWarnings) {
+ *deploymentWarnings << qL1S("Could not replace variable ${") + qL1S(varName)
+ + qL1S("} while parsing ") + fileName;
+ continue;
}
+ string.replace(posBeg, varName.length() + 3, varValue);
+ // varName and varValue most likely have a different length, so we have to adjust
+ posEnd = posEnd - 3 - varName.length() + varValue.length();
}
+ return string;
+}
- // 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 = [&useCache, &deploymentWarnings](ConfigFile &cf) {
- QFile file(cf.filePath);
- if (!file.open(QIODevice::ReadOnly))
- throw Exception("Failed to open config file '%1' for reading.\n").arg(file.fileName());
+ConfigurationData *ConfigurationData::loadFromSource(QIODevice *source, const QString &fileName)
+{
+ try {
+ YamlParser p(source->readAll(), fileName);
+ auto header = p.parseHeader();
+ if (!(header.first == qL1S("am-configuration") && header.second == 1))
+ throw Exception("Unsupported format type and/or version");
+ p.nextDocument();
+
+ QString pwd;
+ if (!fileName.isEmpty())
+ pwd = QFileInfo(fileName).absoluteDir().path();
+
+ QScopedPointer<ConfigurationData> cd(new ConfigurationData);
+
+ YamlParser::Fields fields = {
+ { "runtimes", false, YamlParser::Map, [&cd](YamlParser *p) {
+ cd->runtimes.configurations = p->parseMap(); } },
+ { "containers", false, YamlParser::Map, [&cd](YamlParser *p) {
+ cd->containers.configurations = p->parseMap();
+
+ QVariant containerSelection = cd->containers.configurations.take(qSL("selection"));
+
+
+ QList<QPair<QString, QString>> config;
+
+ // this is easy to get wrong in the config file, so we do not just ignore a map here
+ // (this will in turn trigger the warning below)
+ if (containerSelection.type() == QVariant::Map)
+ containerSelection = QVariantList { containerSelection };
+
+ if (containerSelection.type() == QVariant::String) {
+ config.append(qMakePair(qSL("*"), containerSelection.toString()));
+ } else if (containerSelection.type() == QVariant::List) {
+ QVariantList list = containerSelection.toList();
+ for (const QVariant &v : list) {
+ if (v.type() == QVariant::Map) {
+ QVariantMap map = v.toMap();
+
+ if (map.size() != 1) {
+ qCWarning(LogSystem) << "The container selection configuration needs to be a list of "
+ "single mappings, in order to preserve the evaluation "
+ "order: found a mapping with" << map.size() << "entries.";
+ }
+
+ for (auto it = map.cbegin(); it != map.cend(); ++it)
+ config.append(qMakePair(it.key(), it.value().toString()));
+ }
+ }
+ }
+ cd->containers.selection = config;
+ } },
+ { "installationLocations", false, YamlParser::List, [&cd](YamlParser *p) {
+ cd->installationLocations = p->parseList(); } },
+ { "plugins", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "startup", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->plugins.startup = p->parseStringOrStringList(); } },
+ { "container", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->plugins.container = p->parseStringOrStringList(); } },
+ }); } },
+ { "logging", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "rules", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->logging.rules = p->parseStringOrStringList(); } },
+ { "messagePattern", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->logging.messagePattern = p->parseScalar().toString(); } },
+ { "useAMConsoleLogger", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->logging.useAMConsoleLogger = p->parseScalar(); } },
+ { "dlt", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields( {
+ { "id", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->logging.dlt.id = p->parseScalar().toString(); } },
+ { "description", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->logging.dlt.description = p->parseScalar().toString(); } }
+ }); } }
+ }); } },
+ { "installer", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "disable", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->installer.disable = p->parseScalar().toBool(); } },
+ { "caCertificated", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->installer.caCertificates = p->parseStringOrStringList(); } },
+ { "applicationUserIdSeparation", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "minUserId", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->installer.applicationUserIdSeparation.minUserId = p->parseScalar().toInt(); } },
+ { "maxUserId", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->installer.applicationUserIdSeparation.maxUserId = p->parseScalar().toInt(); } },
+ { "commonGroupId", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->installer.applicationUserIdSeparation.commonGroupId = p->parseScalar().toInt(); } }
+ }); } }
+ }); } },
+ { "quicklaunch", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "idleLoad", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->quicklaunch.idleLoad = p->parseScalar().toDouble(); } },
+ { "runtimesPerContainer", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->quicklaunch.runtimesPerContainer = p->parseScalar().toInt(); } },
+ }); } },
+ { "ui", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "enableTouchEmulation", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.enableTouchEmulation = p->parseScalar().toBool(); } },
+ { "iconThemeSearchPaths", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->ui.iconThemeSearchPaths = p->parseStringOrStringList(); } },
+ { "iconThemeName", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.iconThemeName = p->parseScalar().toString(); } },
+ { "style", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.style = p->parseScalar().toString(); } },
+ { "loadDummyData", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.loadDummyData = p->parseScalar().toBool(); } },
+ { "importPaths", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->ui.importPaths = p->parseStringOrStringList(); } },
+ { "pluginPaths", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->ui.pluginPaths = p->parseStringOrStringList(); } },
+ { "windowIcon", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.windowIcon = p->parseScalar().toString(); } },
+ { "fullscreen", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.fullscreen = p->parseScalar().toBool(); } },
+ { "mainQml", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.mainQml = p->parseScalar().toString(); } },
+ { "resources", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->ui.resources = p->parseStringOrStringList(); } },
+ { "opengl", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "desktopProfile", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.opengl.insert(qSL("desktopProfile"), p->parseScalar().toString()); } },
+ { "esMajorVersion", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.opengl.insert(qSL("esMajorVersion"), p->parseScalar().toInt()); } },
+ { "esMinorVersion", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->ui.opengl.insert(qSL("esMinorVersion"), p->parseScalar().toInt()); } }
+ });
+ } },
+ }); } },
+ { "applications", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "builtinAppsManifestDir", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *p) {
+ cd->applications.builtinAppsManifestDir = p->parseStringOrStringList(); } },
+ { "installedAppsManifestDir", false, YamlParser::Scalar | YamlParser::List, [&cd](YamlParser *) {
+ /* deprecated - ignore */ } },
+ { "installationDir", false, YamlParser::Scalar | YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->applications.installationDir = p->parseScalar().toString(); } },
+ { "documentDir", false, YamlParser::Scalar | YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->applications.documentDir = p->parseScalar().toString(); } }
+ }); } },
+ { "flags", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "forceSingleProcess", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->flags.forceSingleProcess = p->parseScalar().toBool(); } },
+ { "forceMultiProcess", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->flags.forceMultiProcess = p->parseScalar().toBool(); } },
+ { "noSecurity", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->flags.noSecurity = p->parseScalar().toBool(); } },
+ { "developmentMode", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->flags.developmentMode = p->parseScalar().toBool(); } },
+ { "noUiWatchdog", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->flags.noUiWatchdog = p->parseScalar().toBool(); } },
+ }); } },
+ { "systemProperties", false, YamlParser::Map, [&cd](YamlParser *p) {
+ cd->systemProperties = p->parseMap(); } },
+ { "crashAction", false, YamlParser::Map, [&cd](YamlParser *p) {
+ cd->crashAction = p->parseMap(); } },
+ { "intents", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "disable", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->intents.disable = p->parseScalar().toBool(); } },
+ { "timeouts", false, YamlParser::Map, [&cd](YamlParser *p) {
+ p->parseFields({
+ { "disambiguation", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->intents.timeouts.disambiguation = p->parseScalar().toInt(); } },
+ { "startApplication", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->intents.timeouts.startApplication = p->parseScalar().toInt(); } },
+ { "replyFromApplication", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->intents.timeouts.replyFromApplication = p->parseScalar().toInt(); } },
+ { "replyFromSystem", false, YamlParser::Scalar, [&cd](YamlParser *p) {
+ cd->intents.timeouts.replyFromSystem = p->parseScalar().toInt(); } },
+ }); } }
+ }); } },
+ { "dbus", false, YamlParser::Map, [&cd](YamlParser *p) {
+ const QVariantMap dbus = p->parseMap();
+ for (auto it = dbus.cbegin(); it != dbus.cend(); ++it) {
+ const QString &ifaceName = it.key();
+ const QVariantMap &ifaceData = it.value().toMap();
+
+ auto rit = ifaceData.constFind(qSL("register"));
+ if (rit != ifaceData.cend())
+ cd->dbus.registrations.insert(ifaceName, rit->toString());
+
+ auto pit = ifaceData.constFind(qSL("policy"));
+ if (pit != ifaceData.cend())
+ cd->dbus.policies.insert(ifaceName, pit->toMap());
+ }
+ } }
+ };
+
+ p.parseFields(fields);
+ return cd.take();
+ } catch (const Exception &e) {
+ throw Exception(e.errorCode(), "Failed to parse config file %1: %2")
+ .arg(!fileName.isEmpty() ? QDir().relativeFilePath(fileName) : qSL("<stream>"), e.errorString());
+ }
+}
- if (file.size() > 1024*1024)
- throw Exception("Config file '%1' is too big (> 1MB).\n").arg(file.fileName());
- cf.content = substituteVars(file.readAll(), cf.filePath, deploymentWarnings);
+// getters
- QByteArray checksum = QCryptographicHash::hash(cf.content, QCryptographicHash::Sha1);
- if (useCache && (checksum != cf.checksum)) {
- if (deploymentWarnings)
- *deploymentWarnings << qL1S("Failed to read config cache: cached config file checksums do not match current set");
- useCache = false;
+static QString replaceEnvVars(QString string)
+{
+ // note: we cannot replace ${CONFIG_PWD} here, since we have lost that information during
+ // the config file merge!
+
+ static QHash<QString, QString> replacement;
+ if (replacement.isEmpty()) {
+ QMetaEnum locations = QMetaEnum::fromType<QStandardPaths::StandardLocation>();
+ for (int i = 0; i < locations.keyCount(); ++i) {
+ replacement.insert(qSL("stdpath:") + qL1S(locations.key(i)),
+ QStandardPaths::writableLocation(static_cast<QStandardPaths::StandardLocation>(locations.value(i))));
}
- cf.checksum = checksum;
- };
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ QStringList envNames = env.keys();
+ for (const auto &envName : qAsConst(envNames))
+ replacement.insert(qSL("env:") + envName, env.value(envName));
+ }
- try {
- if (configFiles.size() > AM_PARALLEL_THRESHOLD)
- QtConcurrent::blockingMap(configFiles, readConfigFile);
- else
- std::for_each(configFiles.begin(), configFiles.end(), readConfigFile);
- } catch (const Exception &e) {
- showParserMessage(e.errorString(), ErrorMessage);
- exit(1);
+ // this will return immediately, if no vars are referenced
+ int posBeg = -1;
+ int posEnd = -1;
+ while (true) {
+ if ((posBeg = string.indexOf(qL1S("${"), posEnd + 1)) < 0)
+ break;
+ if ((posEnd = string.indexOf('}', posBeg + 2)) < 0)
+ break;
+
+ const QString varName = string.mid(posBeg + 2, posEnd - posBeg - 2);
+ const QString varValue = replacement.value(varName);
+ string.replace(posBeg, varName.length() + 3, varValue);
+
+ // varName and varValue most likely have a different length, so we have to adjust
+ posEnd = posEnd - 3 - varName.length() + varValue.length();
}
+ return string;
+}
-#if defined(AM_TIME_CONFIG_PARSING)
- qCDebug(LogSystem) << "Config parsing" << configFiles.size() << "files: loading finished after"
- << (timer.nsecsElapsed() / 1000) << "usec";
-#endif
- if (useCache) {
- m_config = cache;
- } else if (!configFilePaths.isEmpty()) {
- auto parseConfigFile = [](ConfigFile &cf) {
- try {
- QVector<QVariant> docs = YamlParser::parseAllDocuments(cf.content);
- checkYamlFormat(docs, 2 /*number of expected docs*/, { "am-configuration" }, 1);
- cf.config = docs.at(1).toMap();
- } catch (const Exception &e) {
- throw Exception("Could not parse config file '%1': %2\n")
- .arg(cf.filePath)
- .arg(e.errorString());
- }
- };
+QString Configuration::mainQmlFile() const
+{
+ if (!m_clp.positionalArguments().isEmpty())
+ return m_clp.positionalArguments().at(0);
+ else
+ return replaceEnvVars(m_data->ui.mainQml);
+}
- try {
- if (configFiles.size() > AM_PARALLEL_THRESHOLD)
- QtConcurrent::blockingMap(configFiles, parseConfigFile);
- else
- std::for_each(configFiles.begin(), configFiles.end(), parseConfigFile);
- } catch (const Exception &e) {
- showParserMessage(e.errorString(), ErrorMessage);
- exit(1);
- }
+bool Configuration::noCache() const
+{
+ return value<bool>("no-cache");
+}
- // we cannot parallelize this step, since subsequent config files can overwrite
- // or append to values
- m_config = configFiles.at(0).config;
- for (int i = 1; i < configFiles.size(); ++i)
- mergeConfig(configFiles.at(i).config);
-
- if (!noConfigCache) {
- try {
- QFile cacheFile(cacheFilePath);
- if (!cacheFile.open(QFile::WriteOnly | QFile::Truncate))
- throw Exception(cacheFile, "failed to open file for writing");
-
- QDataStream ds(&cacheFile);
- QVector<QPair<QString, QByteArray>> configChecksums;
- for (const ConfigFile &cf : qAsConst(configFiles))
- configChecksums.append(qMakePair(cf.filePath, cf.checksum));
-
- ds << CacheMagicHeader << configChecksums << m_config;
-
- if (ds.status() != QDataStream::Ok)
- throw Exception("error writing config cache content");
- } catch (const Exception &e) {
- if (deploymentWarnings)
- *deploymentWarnings << qL1S("Failed to write config cache: ") + qL1S(e.what());
- }
- }
-#if defined(AM_TIME_CONFIG_PARSING)
- qCDebug(LogSystem) << "Config parsing" << configFiles.size() << "files: parsing finished after"
- << (timer.nsecsElapsed() / 1000) << "usec";
-#endif
+bool Configuration::clearCache() const
+{
+ return value<bool>("clear-cache");
+}
+
+
+QStringList Configuration::builtinAppsManifestDirs() const
+{
+ return value<QStringList>("builtin-apps-manifest-dir", m_data->applications.builtinAppsManifestDir);
+}
+
+QString Configuration::installationDir() const
+{
+ if (m_installationDir.isEmpty())
+ m_installationDir = value<QString>("installation-dir", m_data->applications.installationDir);
+ return m_installationDir;
+}
+
+QString Configuration::documentDir() const
+{
+ if (m_documentDir.isEmpty())
+ m_documentDir = value<QString>("document-dir", m_data->applications.documentDir);
+ return m_documentDir;
+}
+
+bool Configuration::disableInstaller() const
+{
+ return value<bool>("disable-installer", m_data->installer.disable);
+}
+
+bool Configuration::disableIntents() const
+{
+ return value<bool>("disable-intents", m_data->intents.disable);
+}
+
+int Configuration::intentTimeoutForDisambiguation() const
+{
+ return m_data->intents.timeouts.disambiguation;
+}
+
+int Configuration::intentTimeoutForStartApplication() const
+{
+ return m_data->intents.timeouts.startApplication;
+}
+
+int Configuration::intentTimeoutForReplyFromApplication() const
+{
+ return m_data->intents.timeouts.replyFromApplication;
+}
+
+int Configuration::intentTimeoutForReplyFromSystem() const
+{
+ return m_data->intents.timeouts.replyFromSystem;
+}
+
+bool Configuration::fullscreen() const
+{
+ return value<bool>("fullscreen", m_data->ui.fullscreen);
+}
+
+bool Configuration::noFullscreen() const
+{
+ return value<bool>("no-fullscreen");
+}
+
+QString Configuration::windowIcon() const
+{
+ return m_data->ui.windowIcon;
+}
+
+QStringList Configuration::importPaths() const
+{
+ QStringList importPaths = value<QStringList>("I", m_data->ui.importPaths);
+
+ for (int i = 0; i < importPaths.size(); ++i)
+ importPaths[i] = toAbsoluteFilePath(importPaths.at(i));
+
+ return importPaths;
+}
+
+QStringList Configuration::pluginPaths() const
+{
+ return m_data->ui.pluginPaths;
+}
+
+bool Configuration::verbose() const
+{
+ return value<bool>("verbose") || m_forceVerbose;
+}
+
+void QtAM::Configuration::setForceVerbose(bool forceVerbose)
+{
+ m_forceVerbose = forceVerbose;
+}
+
+bool Configuration::slowAnimations() const
+{
+ return value<bool>("slow-animations");
+}
+
+bool Configuration::loadDummyData() const
+{
+ return value<bool>("load-dummydata", m_data->ui.loadDummyData);
+}
+
+bool Configuration::noSecurity() const
+{
+ return value<bool>("no-security", m_data->flags.noSecurity);
+}
+
+bool Configuration::developmentMode() const
+{
+ return value<bool>("development-mode", m_data->flags.developmentMode);
+}
+
+bool Configuration::noUiWatchdog() const
+{
+ return value<bool>("no-ui-watchdog", m_data->flags.noUiWatchdog);
+}
+
+bool Configuration::noDltLogging() const
+{
+ return value<bool>("no-dlt-logging");
+}
+
+bool Configuration::forceSingleProcess() const
+{
+ return value<bool>("force-single-process", m_data->flags.forceSingleProcess);
+}
+
+bool Configuration::forceMultiProcess() const
+{
+ return value<bool>("force-multi-process", m_data->flags.forceMultiProcess);
+}
+
+bool Configuration::qmlDebugging() const
+{
+ return value<bool>("qml-debug");
+}
+
+QString Configuration::singleApp() const
+{
+ //TODO: single-package
+ return value<QString>("single-app");
+}
+
+QStringList Configuration::loggingRules() const
+{
+ return value<QStringList>("logging-rule", m_data->logging.rules);
+}
+
+QString Configuration::messagePattern() const
+{
+ return m_data->logging.messagePattern;
+}
+
+QVariant Configuration::useAMConsoleLogger() const
+{
+ // true = use the am logger
+ // false = don't use the am logger
+ // invalid = don't use the am logger when QT_MESSAGE_PATTERN is set
+ const QVariant &val = m_data->logging.useAMConsoleLogger;
+ if (val.type() == QVariant::Bool)
+ return val;
+ else
+ return QVariant();
+}
+
+QString Configuration::style() const
+{
+ return m_data->ui.style;
+}
+
+QString Configuration::iconThemeName() const
+{
+ return m_data->ui.iconThemeName;
+}
+
+QStringList Configuration::iconThemeSearchPaths() const
+{
+ return m_data->ui.iconThemeSearchPaths;
+}
+
+bool Configuration::enableTouchEmulation() const
+{
+ return value("enable-touch-emulation", m_data->ui.enableTouchEmulation);
+}
+
+QString Configuration::dltId() const
+{
+ return value<QString>(nullptr, m_data->logging.dlt.id);
+}
+
+QString Configuration::dltDescription() const
+{
+ return value<QString>(nullptr, m_data->logging.dlt.description);
+}
+
+QStringList Configuration::resources() const
+{
+ return m_data->ui.resources;
+}
+
+QVariantMap Configuration::openGLConfiguration() const
+{
+ return m_data->ui.opengl;
+}
+
+QVariantList Configuration::installationLocations() const
+{
+ return m_data->installationLocations;
+}
+
+QList<QPair<QString, QString>> Configuration::containerSelectionConfiguration() const
+{
+ return m_data->containers.selection;
+}
+
+QVariantMap Configuration::containerConfigurations() const
+{
+ return m_data->containers.configurations;
+}
+
+QVariantMap Configuration::runtimeConfigurations() const
+{
+ return m_data->runtimes.configurations;
+}
+
+QVariantMap Configuration::dbusPolicy(const char *interfaceName) const
+{
+ return m_data->dbus.policies.value(qL1S(interfaceName)).toMap();
+}
+
+QString Configuration::dbusRegistration(const char *interfaceName) const
+{
+ auto hasConfig = m_data->dbus.registrations.constFind(qL1S(interfaceName));
+
+ if (hasConfig != m_data->dbus.registrations.cend())
+ return hasConfig->toString();
+ else
+ return m_clp.value(qSL("dbus"));
+}
+
+QVariantMap Configuration::rawSystemProperties() const
+{
+ return m_data->systemProperties;
+}
+
+bool Configuration::applicationUserIdSeparation(uint *minUserId, uint *maxUserId, uint *commonGroupId) const
+{
+ const auto &sep = m_data->installer.applicationUserIdSeparation;
+ if (sep.minUserId >= 0 && sep.maxUserId >= 0 && sep.commonGroupId >= 0) {
+ if (minUserId)
+ *minUserId = sep.minUserId;
+ if (maxUserId)
+ *maxUserId = sep.maxUserId;
+ if (commonGroupId)
+ *commonGroupId = sep.commonGroupId;
+ return true;
}
+ return false;
+}
- const QStringList options = m_clp.values(qSL("o"));
- for (const QString &option : options) {
- QVector<QVariant> docs;
- try {
- docs = YamlParser::parseAllDocuments(option.toUtf8());
- } catch (const Exception &e) {
- showParserMessage(QString::fromLatin1("Could not parse --option value: %1.\n")
- .arg(e.errorString()),
- ErrorMessage);
- exit(1);
- }
+qreal Configuration::quickLaunchIdleLoad() const
+{
+ return m_data->quicklaunch.idleLoad;
+}
- if (docs.size() != 1) {
- showParserMessage(QString::fromLatin1("Could not parse --option value: Invalid document format.\n"),
- ErrorMessage);
- exit(1);
- }
- mergeConfig(docs.at(0).toMap());
+int Configuration::quickLaunchRuntimesPerContainer() const
+{
+ // if you need more than 10 quicklaunchers per runtime, you're probably doing something wrong
+ // or you have a typo in your YAML, which could potentially freeze your target (container
+ // construction can be expensive)
+ return qBound(0, m_data->quicklaunch.runtimesPerContainer, 10);
+}
+
+QString Configuration::waylandSocketName() const
+{
+#if !defined(AM_HEADLESS)
+ QString socketName = m_clp.value(qSL("wayland-socket-name")); // get the default value
+ if (!socketName.isEmpty())
+ return socketName;
+
+ const char *envName = "WAYLAND_DISPLAY";
+ if (qEnvironmentVariableIsSet(envName)) {
+ socketName = qEnvironmentVariable(envName);
+ if (!QGuiApplication::platformName().startsWith(qSL("wayland")) || (socketName != qSL("wayland-0")))
+ return socketName;
}
-#if defined(AM_TIME_CONFIG_PARSING)
- qCDebug(LogSystem) << "Config parsing" << options.size() << "-o options: parsing finished after"
- << (timer.nsecsElapsed() / 1000) << "usec";
+# if defined(Q_OS_LINUX)
+ // modelled after wl_socket_lock() in wayland_server.c
+ const QString xdgDir = qEnvironmentVariable("XDG_RUNTIME_DIR") + qSL("/");
+ const QString pattern = qSL("qtam-wayland-%1");
+ const QString lockSuffix = qSL(".lock");
+
+ for (int i = 0; i < 32; ++i) {
+ socketName = pattern.arg(i);
+ QFile lock(xdgDir + socketName + lockSuffix);
+ if (lock.open(QIODevice::ReadWrite)) {
+ if (::flock(lock.handle(), LOCK_EX | LOCK_NB) == 0) {
+ QFile socket(xdgDir + socketName);
+ if (!socket.exists() || socket.remove())
+ return socketName;
+ }
+ }
+ }
+# endif
#endif
+ return QString();
+
+}
+
+QVariantMap Configuration::managerCrashAction() const
+{
+ return m_data->crashAction;
+}
- // QML cannot cope with invalid QVariants and QDataStream cannot cope with nullptr inside a
- // QVariant ... the workaround is to save invalid variants to the cache and fix them up
- // afterwards:
- fixNullValuesForQml(m_config);
+QStringList Configuration::caCertificates() const
+{
+ return m_data->installer.caCertificates;
+}
+
+QStringList Configuration::pluginFilePaths(const char *type) const
+{
+ if (qstrcmp(type, "startup") == 0)
+ return m_data->plugins.startup;
+ else if (qstrcmp(type, "container") == 0)
+ return m_data->plugins.container;
+ else
+ return QStringList();
+}
+
+QStringList Configuration::testRunnerArguments() const
+{
+ QStringList targs = m_clp.positionalArguments();
+ if (!targs.isEmpty() && targs.constFirst().endsWith(qL1S(".qml")))
+ targs.removeFirst();
+ targs.prepend(QCoreApplication::arguments().constFirst());
+ return targs;
}
QT_END_NAMESPACE_AM
diff --git a/src/main-lib/configuration.h b/src/main-lib/configuration.h
index 7d59d288..dc4a04a1 100644
--- a/src/main-lib/configuration.h
+++ b/src/main-lib/configuration.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -50,45 +51,113 @@
QT_BEGIN_NAMESPACE_AM
+class ConfigurationData;
+
class Configuration
{
public:
+ Configuration(const QStringList &defaultConfigFilePaths, const QString &buildConfigFilePath,
+ const char *additionalDescription = nullptr, bool onlyOnePositionalArgument = true);
+ Configuration(const char *additionalDescription = nullptr, bool onlyOnePositionalArgument = true);
+
virtual ~Configuration();
void parse(QStringList *deploymentWarnings = nullptr);
virtual void parseWithArguments(const QStringList &arguments, QStringList *deploymentWarnings = nullptr);
QVariant buildConfig() const;
-protected:
- Configuration(const QStringList &defaultConfigFilePaths, const QString &buildConfigFilePath);
+ QString mainQmlFile() const;
+
+ bool noCache() const;
+ bool clearCache() const;
+
+ QStringList builtinAppsManifestDirs() const;
+ QString documentDir() const;
+ QString installationDir() const;
+ bool disableInstaller() const;
+ bool disableIntents() const;
+ int intentTimeoutForDisambiguation() const;
+ int intentTimeoutForStartApplication() const;
+ int intentTimeoutForReplyFromApplication() const;
+ int intentTimeoutForReplyFromSystem() const;
+
+ bool fullscreen() const;
+ bool noFullscreen() const;
+ QString windowIcon() const;
+ QStringList importPaths() const;
+ QStringList pluginPaths() const;
+ bool verbose() const;
+ void setForceVerbose(bool forceVerbose);
+ bool slowAnimations() const;
+ bool loadDummyData() const;
+ bool noSecurity() const;
+ bool developmentMode() const;
+ bool noUiWatchdog() const;
+ bool noDltLogging() const;
+ bool forceSingleProcess() const;
+ bool forceMultiProcess() const;
+ bool qmlDebugging() const;
+ QString singleApp() const;
+ QStringList loggingRules() const;
+ QString messagePattern() const;
+ QVariant useAMConsoleLogger() const;
+ QString style() const;
+ QString iconThemeName() const;
+ QStringList iconThemeSearchPaths() const;
+ bool enableTouchEmulation() const;
+ QString dltId() const;
+ QString dltDescription() const;
+ QStringList resources() const;
+
+ QVariantMap openGLConfiguration() const;
+
+ QVariantList installationLocations() const;
+
+ QList<QPair<QString, QString>> containerSelectionConfiguration() const;
+ QVariantMap containerConfigurations() const;
+ QVariantMap runtimeConfigurations() const;
+
+ QVariantMap dbusPolicy(const char *interfaceName) const;
+ QString dbusRegistration(const char *interfaceName) const;
+
+ QVariantMap rawSystemProperties() const;
+
+ bool applicationUserIdSeparation(uint *minUserId, uint *maxUserId, uint *commonGroupId) const;
+ qreal quickLaunchIdleLoad() const;
+ int quickLaunchRuntimesPerContainer() const;
+
+ QString waylandSocketName() const;
+
+ QVariantMap managerCrashAction() const;
+
+ QStringList caCertificates() const;
+
+ QStringList pluginFilePaths(const char *type) const;
+
+ QStringList testRunnerArguments() const;
+
+private:
enum MessageType { UsageMessage, ErrorMessage };
void showParserMessage(const QString &message, MessageType type);
- template <typename T> T value(const char *clname, const QVector<const char *> &cfname = QVector<const char *>()) const
+ template <typename T> T value(const char *clname, const T &cfvalue = T()) const
{
Q_UNUSED(clname)
- Q_UNUSED(cfname)
+ Q_UNUSED(cfvalue)
return T();
}
-private:
- QVariant findInConfigFile(const QVector<const char *> &path, bool *found = nullptr) const;
- void mergeConfig(const QVariantMap &other);
- static QByteArray substituteVars(const QByteArray &str, const QString &fileName,
- QStringList *deploymentWarnings = nullptr);
-
-protected:
QStringList m_defaultConfigFilePaths;
QString m_buildConfigFilePath;
QCommandLineParser m_clp;
- QVariantMap m_config;
+ ConfigurationData *m_data;
+ QString m_mainQmlFile;
+ bool m_onlyOnePositionalArgument = false;
+ bool m_forceVerbose = false;
+ mutable QString m_installationDir; // cached value
+ mutable QString m_documentDir; // cached value
};
-template<> bool Configuration::value(const char *clname, const QVector<const char *> &cfname) const;
-template<> QString Configuration::value(const char *clname, const QVector<const char *> &cfname) const;
-template<> QStringList Configuration::value(const char *clname, const QVector<const char *> &cfname) const;
-template<> QVariant Configuration::value(const char *clname, const QVector<const char *> &cfname) const;
-
QT_END_NAMESPACE_AM
diff --git a/src/main-lib/configuration_p.h b/src/main-lib/configuration_p.h
new file mode 100644
index 00000000..e97f9f2f
--- /dev/null
+++ b/src/main-lib/configuration_p.h
@@ -0,0 +1,171 @@
+/****************************************************************************
+**
+** 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 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 <QtAppManCommon/global.h>
+#include <QStringList>
+#include <QVariantMap>
+#include <QVector>
+#include <QSet>
+
+QT_FORWARD_DECLARE_CLASS(QIODevice)
+
+QT_BEGIN_NAMESPACE_AM
+
+
+// IMPORTANT: if you add/remove/change anything in this struct, you also have to adjust the
+// loadFromCache(), saveToCache() and mergeFrom() functions in the cpp file!
+
+struct ConfigurationData
+{
+ static ConfigurationData *loadFromSource(QIODevice *source, const QString &fileName);
+ static QByteArray substituteVars(const QByteArray &sourceContent, const QString &fileName,
+ QStringList *deploymentWarnings = nullptr);
+ static ConfigurationData *loadFromCache(QDataStream &ds);
+ void saveToCache(QDataStream &ds) const;
+ void mergeFrom(const ConfigurationData *from);
+
+
+ struct Runtimes {
+ QVariantMap configurations;
+ } runtimes;
+
+ struct {
+ QVariantMap configurations;
+ QList<QPair<QString, QString>> selection;
+ } containers;
+
+ struct {
+ bool disable = false;
+ struct {
+ int disambiguation = 10000;
+ int startApplication = 3000;
+ int replyFromApplication = 5000;
+ int replyFromSystem = 20000;
+ } timeouts;
+ } intents;
+
+ struct {
+ QStringList startup;
+ QStringList container;
+ } plugins;
+
+ struct {
+ struct {
+ QString id;
+ QString description;
+ } dlt;
+ QStringList rules;
+ QString messagePattern;
+ QVariant useAMConsoleLogger; // true / false / invalid
+ } logging;
+
+ struct {
+ bool disable = false;
+ QStringList caCertificates;
+ struct {
+ int minUserId = -1;
+ int maxUserId = -1;
+ int commonGroupId = -1;
+ } applicationUserIdSeparation;
+ } installer;
+
+ struct {
+ QVariantMap policies;
+ QVariantMap registrations;
+ } dbus;
+
+ struct {
+ double idleLoad = 0.;
+ int runtimesPerContainer = 0;
+ } quicklaunch;
+
+ struct {
+ QVariantMap opengl;
+ bool enableTouchEmulation = false;
+ QStringList iconThemeSearchPaths;
+ QString iconThemeName;
+ QString style;
+ bool loadDummyData = false;
+ QStringList importPaths;
+ QStringList pluginPaths;
+ QString windowIcon;
+ bool fullscreen = false;
+ QString mainQml;
+ QStringList resources;
+ } ui;
+
+ struct {
+ QStringList builtinAppsManifestDir;
+ QString installationDir;
+ QString documentDir;
+ } applications; // TODO: rename to package?
+
+ QVariantList installationLocations;
+
+ QVariantMap crashAction;
+ QVariantMap systemProperties;
+
+ struct {
+ bool forceSingleProcess = false;
+ bool forceMultiProcess = false;
+ bool noSecurity = false;
+ bool developmentMode = false;
+ bool noUiWatchdog = false;
+ } flags;
+
+ // command-line only:
+ struct {
+ QString waylandSocketName;
+ bool noFullscreen = false;
+ bool verbose = false;
+ bool slowAnimations = false;
+ bool noDltLogging = false;
+ bool singlePackage = false;
+ bool qmlDebug = false;
+ bool touchEmulation = false;
+ } commandLine;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/main-lib/defaultconfiguration.cpp b/src/main-lib/defaultconfiguration.cpp
index 49fe47aa..f83ec52a 100644
--- a/src/main-lib/defaultconfiguration.cpp
+++ b/src/main-lib/defaultconfiguration.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -40,535 +41,3 @@
**
****************************************************************************/
-#include <QFileInfo>
-#include <QCoreApplication>
-#include <QFile>
-#include <QDebug>
-
-#if defined(Q_OS_LINUX)
-# include <sys/file.h>
-#endif
-
-#if !defined(AM_HEADLESS)
-# include <QGuiApplication>
-#endif
-
-#include <QtAppManCommon/logging.h>
-
-#include "defaultconfiguration.h"
-#include "utilities.h"
-
-QT_BEGIN_NAMESPACE_AM
-
-
-DefaultConfiguration::DefaultConfiguration(const char *additionalDescription,
- bool onlyOnePositionalArgument)
- : DefaultConfiguration(QStringList(), qSL(":/build-config.yaml"),
- additionalDescription, onlyOnePositionalArgument)
-{ }
-
-DefaultConfiguration::DefaultConfiguration(const QStringList &defaultConfigFilePaths,
- const QString &buildConfigFilePath,
- const char *additionalDescription,
- bool onlyOnePositionalArgument)
- : Configuration(defaultConfigFilePaths, buildConfigFilePath)
- , m_onlyOnePositionalArgument(onlyOnePositionalArgument)
-{
- // using QStringLiteral for all strings here adds a few KB of ro-data, but will also improve
- // startup times slightly: less allocations and copies. MSVC cannot cope with multi-line though
-
- const char *description =
- "In addition to the commandline options below, the following environment\n"
- "variables can be set:\n\n"
- " AM_STARTUP_TIMER if set to 1, a startup performance analysis will be printed\n"
- " on the console. Anything other than 1 will be interpreted\n"
- " as the name of a file that is used instead of the console.\n"
- "\n"
- " AM_FORCE_COLOR_OUTPUT can be set to 'on' to force color output to the console\n"
- " and to 'off' to disable it. Any other value will result\n"
- " in the default, auto-detection behavior.\n";
-
- m_clp.setApplicationDescription(qSL("\n") + QCoreApplication::applicationName() + qSL("\n\n")
- + (additionalDescription ? (qL1S(additionalDescription) + qSL("\n\n")) : QString())
- + qL1S(description));
-
- m_clp.addPositionalArgument(qSL("qml-file"), qSL("the main QML file."));
- m_clp.addOption({ qSL("database"), qSL("filepath of the application database cache."), qSL("file") });
- m_clp.addOption({ { qSL("r"), qSL("recreate-database") }, qSL("recreate the application database cache.") });
- m_clp.addOption({ qSL("builtin-apps-manifest-dir"), qSL("base directory for built-in application manifests."), qSL("dir") });
- m_clp.addOption({ qSL("installation-dir"), qSL("base directory for package installations."), qSL("dir") });
- m_clp.addOption({ qSL("document-dir"), qSL("base directory for per-package document directories."), qSL("dir") });
- m_clp.addOption({ qSL("installed-apps-manifest-dir"), qSL("deprecated (ignored)."), qSL("dir") });
- m_clp.addOption({ qSL("app-image-mount-dir"), qSL("deprecated, not needed anymore."), qSL("dir") });
- m_clp.addOption({ qSL("disable-installer"), qSL("disable the application installer sub-system.") });
- m_clp.addOption({ qSL("disable-intents"), qSL("disable the intents sub-system.") });
-#if defined(QT_DBUS_LIB)
- m_clp.addOption({ qSL("dbus"), qSL("register on the specified D-Bus."), qSL("<bus>|system|session|none|auto"), qSL("auto") });
- m_clp.addOption({ qSL("start-session-dbus"), qSL("deprecated (ignored).") });
-#endif
- m_clp.addOption({ qSL("fullscreen"), qSL("display in full-screen.") });
- m_clp.addOption({ qSL("no-fullscreen"), qSL("do not display in full-screen.") });
- m_clp.addOption({ qSL("I"), qSL("additional QML import path."), qSL("dir") });
- m_clp.addOption({ qSL("verbose"), qSL("verbose output.") });
- m_clp.addOption({ qSL("slow-animations"), qSL("run all animations in slow motion.") });
- m_clp.addOption({ qSL("load-dummydata"), qSL("loads QML dummy-data.") });
- m_clp.addOption({ qSL("no-security"), qSL("disables all security related checks (dev only!)") });
- m_clp.addOption({ qSL("development-mode"), qSL("enable development mode, allowing installation of dev-signed packages.") });
- m_clp.addOption({ qSL("no-ui-watchdog"), qSL("disables detecting hung UI applications (e.g. via Wayland's ping/pong).") });
- m_clp.addOption({ qSL("no-dlt-logging"), qSL("disables logging using automotive DLT.") });
- m_clp.addOption({ qSL("force-single-process"), qSL("forces single-process mode even on a wayland enabled build.") });
- m_clp.addOption({ qSL("force-multi-process"), qSL("forces multi-process mode. Will exit immediately if this is not possible.") });
- m_clp.addOption({ qSL("wayland-socket-name"), qSL("use this file name to create the wayland socket."), qSL("socket") });
- m_clp.addOption({ qSL("single-app"), qSL("runs a single application only (ignores the database)"), qSL("info.yaml file") });
- m_clp.addOption({ qSL("logging-rule"), qSL("adds a standard Qt logging rule."), qSL("rule") });
- m_clp.addOption({ qSL("qml-debug"), qSL("enables QML debugging and profiling.") });
- m_clp.addOption({ qSL("enable-touch-emulation"), qSL("enables the touch emulation, converting mouse to touch events.") });
-}
-
-DefaultConfiguration::~DefaultConfiguration()
-{
-}
-
-
-void DefaultConfiguration::parseWithArguments(const QStringList &arguments, QStringList *deploymentWarnings)
-{
- Configuration::parseWithArguments(arguments, deploymentWarnings);
-
- if (m_onlyOnePositionalArgument && (m_clp.positionalArguments().size() > 1)) {
- showParserMessage(qL1S("Only one main qml file can be specified.\n"), ErrorMessage);
- exit(1);
- }
-
- if (!deploymentWarnings)
- return;
-
- if (database().isEmpty())
- *deploymentWarnings << qL1S("No --database command line parameter or applications/database configuration"
- " key specified. Database won't be cached to speed up subsequent System-UI startups.");
-
- if (installationDir().isEmpty()) {
- const auto ilocs = value<QVariant>(nullptr, { "installationLocations" }).toList();
- if (!ilocs.isEmpty()) {
- *deploymentWarnings << qL1S("Support for \"installationLocations\" in the main config file has been removed:");
-
- for (const auto iloc : ilocs) {
- QVariantMap map = iloc.toMap();
- QString id = map.value(qSL("id")).toString();
- if (id == qSL("internal-0")) {
- m_installationDir = map.value(qSL("installationPath")).toString();
- m_documentDir = map.value(qSL("documentPath")).toString();
- *deploymentWarnings << qL1S(" * still using installation location \"internal-0\" for backward compatibility");
- } else {
- *deploymentWarnings << qL1S(" * ignoring installation location ") + id;
- }
- }
- }
- }
-
- if (installationDir().isEmpty()) {
- *deploymentWarnings << qL1S("No --installation-dir command line parameter or"
- " applications/installationDir configuration key specified. It won't be possible to install,"
- " remove or access installable packages.");
- }
-
- if (value<bool>("start-session-dbus"))
- *deploymentWarnings << qL1S("Option \"--start-session-dbus\" has been deprecated and will be ignored.");
-}
-
-QString DefaultConfiguration::mainQmlFile() const
-{
- if (!m_clp.positionalArguments().isEmpty())
- return m_clp.positionalArguments().at(0);
- else
- return value<QString>(nullptr, { "ui", "mainQml" });
-}
-
-
-QString DefaultConfiguration::database() const
-{
- return value<QString>("database", { "applications", "database" });
-}
-
-bool DefaultConfiguration::recreateDatabase() const
-{
- return value<bool>("recreate-database");
-}
-
-QStringList DefaultConfiguration::builtinAppsManifestDirs() const
-{
- return value<QStringList>("builtin-apps-manifest-dir", { "applications", "builtinAppsManifestDir" });
-}
-
-QString DefaultConfiguration::installationDir() const
-{
- if (m_installationDir.isEmpty())
- m_installationDir = value<QString>("installation-dir", { "applications", "installationDir" });
- return m_installationDir;
-}
-
-QString DefaultConfiguration::documentDir() const
-{
- if (m_documentDir.isEmpty())
- m_documentDir = value<QString>("document-dir", { "applications", "documentDir" });
- return m_documentDir;
-}
-
-bool DefaultConfiguration::disableInstaller() const
-{
- return value<bool>("disable-installer", { "installer", "disable" });
-}
-
-bool DefaultConfiguration::disableIntents() const
-{
- return value<bool>("disable-intents", { "intents", "disable" });
-}
-
-QMap<QString, int> DefaultConfiguration::intentTimeouts() const
-{
- QVariantMap map = value<QVariant>(nullptr, { "intents", "timeouts" }).toMap();
- QMap<QString, int> timeouts;
-
- for (auto it = map.cbegin(); it != map.cend(); ++it) {
- QVariant v = it.value();
- if (v.canConvert<int>())
- timeouts.insert(it.key(), v.toInt());
- }
- return timeouts;
-}
-
-
-bool DefaultConfiguration::fullscreen() const
-{
- return value<bool>("fullscreen", { "ui", "fullscreen" });
-}
-
-bool DefaultConfiguration::noFullscreen() const
-{
- return value<bool>("no-fullscreen");
-}
-
-QString DefaultConfiguration::windowIcon() const
-{
- return value<QString>(nullptr, { "ui", "windowIcon" });
-}
-
-QStringList DefaultConfiguration::importPaths() const
-{
- QStringList importPaths = value<QStringList>("I", { "ui", "importPaths" });
-
- for (int i = 0; i < importPaths.size(); ++i)
- importPaths[i] = toAbsoluteFilePath(importPaths.at(i));
-
- return importPaths;
-}
-
-QStringList DefaultConfiguration::pluginPaths() const
-{
- QStringList pluginPaths = value<QStringList>(nullptr, { "ui", "pluginPaths" });
-
- for (int i = 0; i < pluginPaths.size(); ++i)
- pluginPaths[i] = QFileInfo(pluginPaths.at(i)).absoluteFilePath();
-
- return pluginPaths;
-}
-
-bool DefaultConfiguration::verbose() const
-{
- return value<bool>("verbose") || m_forceVerbose;
-}
-
-void QtAM::DefaultConfiguration::setForceVerbose(bool forceVerbose)
-{
- m_forceVerbose = forceVerbose;
-}
-
-bool DefaultConfiguration::slowAnimations() const
-{
- return value<bool>("slow-animations");
-}
-
-bool DefaultConfiguration::loadDummyData() const
-{
- return value<bool>("load-dummydata", { "ui", "loadDummyData" });
-}
-
-bool DefaultConfiguration::noSecurity() const
-{
- return value<bool>("no-security", { "flags", "noSecurity" });
-}
-
-bool DefaultConfiguration::developmentMode() const
-{
- return value<bool>("development-mode", { "flags", "developmentMode" });
-}
-
-bool DefaultConfiguration::noUiWatchdog() const
-{
- return value<bool>("no-ui-watchdog", { "flags", "noUiWatchdog" });
-}
-
-bool DefaultConfiguration::noDltLogging() const
-{
- return value<bool>("no-dlt-logging");
-}
-
-bool DefaultConfiguration::forceSingleProcess() const
-{
- return value<bool>("force-single-process", { "flags", "forceSingleProcess" });
-}
-
-bool DefaultConfiguration::forceMultiProcess() const
-{
- return value<bool>("force-multi-process", { "flags", "forceMultiProcess" });
-}
-
-bool DefaultConfiguration::qmlDebugging() const
-{
- return value<bool>("qml-debug");
-}
-
-QString DefaultConfiguration::singleApp() const
-{
- return value<QString>("single-app");
-}
-
-QStringList DefaultConfiguration::loggingRules() const
-{
- return value<QStringList>("logging-rule", { "logging", "rules" });
-}
-
-QString DefaultConfiguration::messagePattern() const
-{
- return value<QString>(nullptr, { "logging", "messagePattern" });
-}
-
-QVariant DefaultConfiguration::useAMConsoleLogger() const
-{
- // true = use the am logger
- // false = don't use the am logger
- // invalid = don't use the am logger when QT_MESSAGE_PATTERN is set
- QVariant val = value<QVariant>(nullptr, { "logging", "useAMConsoleLogger" });
- if (val.type() == QVariant::Bool)
- return val;
- else
- return QVariant();
-}
-
-QString DefaultConfiguration::style() const
-{
- return value<QString>(nullptr, { "ui", "style" });
-}
-
-QString DefaultConfiguration::iconThemeName() const
-{
- return value<QString>(nullptr, { "ui", "iconThemeName" });
-}
-
-QStringList DefaultConfiguration::iconThemeSearchPaths() const
-{
- return value<QStringList>(nullptr, { "ui", "iconThemeSearchPaths" });
-}
-
-bool DefaultConfiguration::enableTouchEmulation() const
-{
- return value<bool>("enable-touch-emulation", { "ui", "enableTouchEmulation" });
-}
-
-QString DefaultConfiguration::dltId() const
-{
- return value<QString>(nullptr, { "logging", "dlt", "id" });
-}
-
-QString DefaultConfiguration::dltDescription() const
-{
- return value<QString>(nullptr, { "logging", "dlt", "description" });
-}
-
-QStringList DefaultConfiguration::resources() const
-{
- return value<QStringList>(nullptr, { "ui", "resources" });
-}
-
-QVariantMap DefaultConfiguration::openGLConfiguration() const
-{
- return value<QVariant>(nullptr, { "ui", "opengl" }).toMap();
-}
-
-QList<QPair<QString, QString>> DefaultConfiguration::containerSelectionConfiguration() const
-{
- QList<QPair<QString, QString>> config;
- QVariant containerSelection = value<QVariant>(nullptr, { "containers", "selection" });
-
- // this is easy to get wrong in the config file, so we do not just ignore a map here
- // (this will in turn trigger the warning below)
- if (containerSelection.type() == QVariant::Map)
- containerSelection = QVariantList { containerSelection };
-
- if (containerSelection.type() == QVariant::String) {
- config.append(qMakePair(qSL("*"), containerSelection.toString()));
- } else if (containerSelection.type() == QVariant::List) {
- QVariantList list = containerSelection.toList();
- for (const QVariant &v : list) {
- if (v.type() == QVariant::Map) {
- QVariantMap map = v.toMap();
-
- if (map.size() != 1) {
- qCWarning(LogSystem) << "The container selection configuration needs to be a list of "
- "single mappings, in order to preserve the evaluation "
- "order: found a mapping with" << map.size() << "entries.";
- }
-
- for (auto it = map.cbegin(); it != map.cend(); ++it)
- config.append(qMakePair(it.key(), it.value().toString()));
- }
- }
- }
- return config;
-}
-
-QVariantMap DefaultConfiguration::containerConfigurations() const
-{
- QVariantMap map = value<QVariant>(nullptr, { "containers" }).toMap();
- map.remove(qSL("selection"));
- return map;
-}
-
-QVariantMap DefaultConfiguration::runtimeConfigurations() const
-{
- return value<QVariant>(nullptr, { "runtimes" }).toMap();
-}
-
-QVariantMap DefaultConfiguration::dbusPolicy(const char *interfaceName) const
-{
- return value<QVariant>(nullptr, { "dbus", interfaceName, "policy" }).toMap();
-}
-
-QString DefaultConfiguration::dbusRegistration(const char *interfaceName) const
-{
- return value<QString>("dbus", { "dbus", interfaceName, "register" });
-}
-
-QVariantMap DefaultConfiguration::rawSystemProperties() const
-{
- return value<QVariant>(nullptr, { "systemProperties" }).toMap();
-}
-
-bool DefaultConfiguration::applicationUserIdSeparation(uint *minUserId, uint *maxUserId, uint *commonGroupId) const
-{
- bool found = false;
- QVariantMap map = value<QVariant>(nullptr, { "installer", "applicationUserIdSeparation" }).toMap();
-
- if (found) {
- auto idFromMap = [&map](const char *key) -> uint {
- bool ok;
- uint value = map.value(qL1S(key)).toUInt(&ok);
- return ok ? value : uint(-1);
- };
-
- uint undef = uint(-1);
- uint minUser = idFromMap("minUserId");
- uint maxUser = idFromMap("maxUserId");
- uint commonGroup = idFromMap("commonGroupId");
-
- if (minUser != undef && maxUser != undef && commonGroup != undef && minUser < maxUser) {
- if (minUserId)
- *minUserId = minUser;
- if (maxUserId)
- *maxUserId = maxUser;
- if (commonGroupId)
- *commonGroupId = commonGroup;
- return true;
- }
- }
- return false;
-}
-
-qreal DefaultConfiguration::quickLaunchIdleLoad() const
-{
- return value<QVariant>(nullptr, { "quicklaunch", "idleLoad" }).toReal();
-}
-
-int DefaultConfiguration::quickLaunchRuntimesPerContainer() const
-{
- int rpc = value<QVariant>(nullptr, { "quicklaunch", "runtimesPerContainer" }).toInt();
-
- // if you need more than 10 quicklaunchers per runtime, you're probably doing something wrong
- // or you have a typo in your YAML, which could potentially freeze your target (container
- // construction can be expensive)
- return qBound(0, rpc, 10);
-}
-
-QString DefaultConfiguration::waylandSocketName() const
-{
-#if !defined(AM_HEADLESS)
- QString socketName = m_clp.value(qSL("wayland-socket-name")); // get the default value
- if (!socketName.isEmpty())
- return socketName;
-
- const char *envName = "WAYLAND_DISPLAY";
- if (qEnvironmentVariableIsSet(envName)) {
- socketName = qEnvironmentVariable(envName);
- if (!QGuiApplication::platformName().startsWith(qSL("wayland")) || (socketName != qSL("wayland-0")))
- return socketName;
- }
-
-# if defined(Q_OS_LINUX)
- // modelled after wl_socket_lock() in wayland_server.c
- const QString xdgDir = qEnvironmentVariable("XDG_RUNTIME_DIR") + qSL("/");
- const QString pattern = qSL("qtam-wayland-%1");
- const QString lockSuffix = qSL(".lock");
-
- for (int i = 0; i < 32; ++i) {
- socketName = pattern.arg(i);
- QFile lock(xdgDir + socketName + lockSuffix);
- if (lock.open(QIODevice::ReadWrite)) {
- if (::flock(lock.handle(), LOCK_EX | LOCK_NB) == 0) {
- QFile socket(xdgDir + socketName);
- if (!socket.exists() || socket.remove())
- return socketName;
- }
- }
- }
-# endif
-#endif
- return QString();
-}
-
-QString DefaultConfiguration::telnetAddress() const
-{
- QString s = value<QString>(nullptr, { "debug", "telnetAddress" });
- if (s.isEmpty())
- s = qSL("0.0.0.0");
- return s;
-}
-
-quint16 DefaultConfiguration::telnetPort() const
-{
- return value<QVariant>(nullptr, { "debug", "telnetPort" }).value<quint16>();
-}
-
-QVariantMap DefaultConfiguration::managerCrashAction() const
-{
- return value<QVariant>(nullptr, { "crashAction"} ).toMap();
-}
-
-QStringList DefaultConfiguration::caCertificates() const
-{
- return value<QStringList>(nullptr, { "installer", "caCertificates" });
-}
-
-QStringList DefaultConfiguration::pluginFilePaths(const char *type) const
-{
- return value<QStringList>(nullptr, { "plugins", type });
-}
-
-QStringList DefaultConfiguration::testRunnerArguments() const
-{
- QStringList targs = m_clp.positionalArguments();
- if (!targs.isEmpty() && targs.constFirst().endsWith(qL1S(".qml")))
- targs.removeFirst();
- targs.prepend(QCoreApplication::arguments().constFirst());
- return targs;
-}
-
-QT_END_NAMESPACE_AM
diff --git a/src/main-lib/defaultconfiguration.h b/src/main-lib/defaultconfiguration.h
index 25129719..2245ae2d 100644
--- a/src/main-lib/defaultconfiguration.h
+++ b/src/main-lib/defaultconfiguration.h
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
@@ -47,95 +48,6 @@
QT_BEGIN_NAMESPACE_AM
-class DefaultConfiguration : public Configuration
-{
-public:
- DefaultConfiguration(const char *additionalDescription = nullptr,
- bool onlyOnePositionalArgument = true);
- DefaultConfiguration(const QStringList &defaultConfigFilePaths,
- const QString &buildConfigFilePath,
- const char *additionalDescription = nullptr,
- bool onlyOnePositionalArgument = true);
- ~DefaultConfiguration() override;
-
- void parseWithArguments(const QStringList &arguments, QStringList *deploymentWarnings = nullptr) override;
-
- QString mainQmlFile() const;
- QString database() const;
- bool recreateDatabase() const;
-
- QStringList builtinAppsManifestDirs() const;
-
- QString installationDir() const;
- QString documentDir() const;
-
- bool disableInstaller() const;
- bool disableIntents() const;
- QMap<QString, int> intentTimeouts() const;
-
- bool fullscreen() const;
- bool noFullscreen() const;
- QString windowIcon() const;
- QStringList importPaths() const;
- QStringList pluginPaths() const;
- bool verbose() const;
- void setForceVerbose(bool forceVerbose);
- bool slowAnimations() const;
- bool loadDummyData() const;
- bool noSecurity() const;
- bool developmentMode() const;
- bool noUiWatchdog() const;
- bool noDltLogging() const;
- bool forceSingleProcess() const;
- bool forceMultiProcess() const;
- bool qmlDebugging() const;
- QString singleApp() const;
- QStringList loggingRules() const;
- QString messagePattern() const;
- QVariant useAMConsoleLogger() const;
- QString style() const;
- QString iconThemeName() const;
- QStringList iconThemeSearchPaths() const;
- bool enableTouchEmulation() const;
- QString dltId() const;
- QString dltDescription() const;
- QStringList resources() const;
-
- QVariantMap openGLConfiguration() const;
-
- QList<QPair<QString, QString>> containerSelectionConfiguration() const;
- QVariantMap containerConfigurations() const;
- QVariantMap runtimeConfigurations() const;
-
- QVariantMap dbusPolicy(const char *interfaceName) const;
- QString dbusRegistration(const char *interfaceName) const;
-
- QVariantMap rawSystemProperties() const;
-
- bool applicationUserIdSeparation(uint *minUserId, uint *maxUserId, uint *commonGroupId) const;
-
- qreal quickLaunchIdleLoad() const;
- int quickLaunchRuntimesPerContainer() const;
-
- QString waylandSocketName() const;
-
- QString telnetAddress() const;
- quint16 telnetPort() const;
-
- QVariantMap managerCrashAction() const;
-
- QStringList caCertificates() const;
-
- QStringList pluginFilePaths(const char *type) const;
-
- QStringList testRunnerArguments() const;
-
-private:
- QString m_mainQmlFile;
- bool m_onlyOnePositionalArgument = false;
- bool m_forceVerbose = false;
- mutable QString m_installationDir; // cached value
- mutable QString m_documentDir; // cached value
-};
+using DefaultConfiguration = Configuration;
QT_END_NAMESPACE_AM
diff --git a/src/main-lib/main-lib.pro b/src/main-lib/main-lib.pro
index 721a54ce..052b57df 100644
--- a/src/main-lib/main-lib.pro
+++ b/src/main-lib/main-lib.pro
@@ -32,19 +32,20 @@ DEFINES += AM_BUILD_DIR=\\\"$$BUILD_DIR\\\"
HEADERS += \
configuration.h \
main.h \
+ configuration_p.h \
defaultconfiguration.h \
+ applicationinstaller.h \
!headless:HEADERS += \
windowframetimer.h \
- applicationinstaller.h \
SOURCES += \
main.cpp \
configuration.cpp \
defaultconfiguration.cpp \
+ applicationinstaller.cpp \
!headless:SOURCES += \
windowframetimer.cpp \
- applicationinstaller.cpp \
load(qt_module)
diff --git a/src/main-lib/main.cpp b/src/main-lib/main.cpp
index ce5ff211..ffd17dda 100644
--- a/src/main-lib/main.cpp
+++ b/src/main-lib/main.cpp
@@ -94,7 +94,7 @@
#include "global.h"
#include "logging.h"
#include "main.h"
-#include "defaultconfiguration.h"
+#include "configuration.h"
#include "applicationmanager.h"
#include "packagemanager.h"
#include "packagedatabase.h"
@@ -203,7 +203,7 @@ Main::~Main()
The caller has to make sure that cfg will be available even after this function returns:
we will access the cfg object from delayed init functions via lambdas!
*/
-void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentWarnings) Q_DECL_NOEXCEPT_EXPR(false)
+void Main::setup(const Configuration *cfg, const QStringList &deploymentWarnings) Q_DECL_NOEXCEPT_EXPR(false)
{
// basics that are needed in multiple setup functions below
m_noSecurity = cfg->noSecurity();
@@ -237,13 +237,15 @@ void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentW
cfg->containerConfigurations(), cfg->pluginFilePaths("container"),
cfg->iconThemeSearchPaths(), cfg->iconThemeName());
- loadPackageDatabase(cfg->recreateDatabase(), cfg->singleApp());
+ loadPackageDatabase(cfg->clearCache() || cfg->noCache(), cfg->singleApp());
setupSingletons(cfg->containerSelectionConfiguration(), cfg->quickLaunchRuntimesPerContainer(),
cfg->quickLaunchIdleLoad());
- if (!cfg->disableIntents())
- setupIntents(cfg->intentTimeouts());
+ if (!cfg->disableIntents()) {
+ setupIntents(cfg->intentTimeoutForDisambiguation(), cfg->intentTimeoutForStartApplication(),
+ cfg->intentTimeoutForReplyFromApplication(), cfg->intentTimeoutForReplyFromSystem());
+ }
registerPackages();
@@ -251,7 +253,7 @@ void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentW
StartupTimer::instance()->checkpoint("skipping installer");
} else {
setupInstaller(cfg->caCertificates(),
- std::bind(&DefaultConfiguration::applicationUserIdSeparation, cfg,
+ std::bind(&Configuration::applicationUserIdSeparation, cfg,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
setLibraryPaths(libraryPaths() + cfg->pluginPaths());
@@ -259,11 +261,11 @@ void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentW
setupWindowTitle(QString(), cfg->windowIcon());
setupWindowManager(cfg->waylandSocketName(), cfg->slowAnimations(), cfg->noUiWatchdog());
setupTouchEmulation(cfg->enableTouchEmulation());
- setupShellServer(cfg->telnetAddress(), cfg->telnetPort());
+ setupShellServer(QString(), 0); // remove
setupSSDPService();
- setupDBus(std::bind(&DefaultConfiguration::dbusRegistration, cfg, std::placeholders::_1),
- std::bind(&DefaultConfiguration::dbusPolicy, cfg, std::placeholders::_1));
+ setupDBus(std::bind(&Configuration::dbusRegistration, cfg, std::placeholders::_1),
+ std::bind(&Configuration::dbusPolicy, cfg, std::placeholders::_1));
}
bool Main::isSingleProcessMode() const
@@ -456,10 +458,14 @@ void Main::loadPackageDatabase(bool recreateDatabase, const QString &singlePacka
StartupTimer::instance()->checkpoint("after package database loading");
}
-void Main::setupIntents(const QMap<QString, int> &timeouts) Q_DECL_NOEXCEPT_EXPR(false)
+void Main::setupIntents(int disambiguationTimeout, int startApplicationTimeout,
+ int replyFromApplicationTimeout, int replyFromSystemTimeout) Q_DECL_NOEXCEPT_EXPR(false)
{
m_intentServer = IntentAMImplementation::createIntentServerAndClientInstance(m_packageManager,
- timeouts);
+ disambiguationTimeout,
+ startApplicationTimeout,
+ replyFromApplicationTimeout,
+ replyFromSystemTimeout);
StartupTimer::instance()->checkpoint("after IntentServer instantiation");
}
diff --git a/src/main-lib/main.h b/src/main-lib/main.h
index 084c6747..9e3b5992 100644
--- a/src/main-lib/main.h
+++ b/src/main-lib/main.h
@@ -83,8 +83,7 @@ class IntentServer;
class WindowManager;
class QuickLauncher;
class SystemMonitor;
-class DefaultConfiguration;
-
+class Configuration;
class Main : public MainBase, protected SharedMain
{
@@ -97,7 +96,7 @@ public:
bool isSingleProcessMode() const;
- void setup(const DefaultConfiguration *cfg, const QStringList &deploymentWarnings = QStringList()) Q_DECL_NOEXCEPT_EXPR(false);
+ void setup(const Configuration *cfg, const QStringList &deploymentWarnings = QStringList()) Q_DECL_NOEXCEPT_EXPR(false);
void loadQml(bool loadDummyData) Q_DECL_NOEXCEPT_EXPR(false);
void showWindow(bool showFullscreen);
@@ -117,7 +116,8 @@ protected:
const QVariantMap &containerConfigurations, const QStringList &containerPluginPaths,
const QStringList &iconThemeSearchPaths, const QString &iconThemeName);
void loadPackageDatabase(bool recreateDatabase, const QString &singlePackage) Q_DECL_NOEXCEPT_EXPR(false);
- void setupIntents(const QMap<QString, int> &timeouts) Q_DECL_NOEXCEPT_EXPR(false);
+ void setupIntents(int disambiguationTimeout, int startApplicationTimeout,
+ int replyFromApplicationTimeout, int replyFromSystemTimeout) Q_DECL_NOEXCEPT_EXPR(false);
void setupSingletons(const QList<QPair<QString, QString>> &containerSelectionConfiguration,
int quickLaunchRuntimesPerContainer, qreal quickLaunchIdleLoad) Q_DECL_NOEXCEPT_EXPR(false);
void setupInstaller(const QStringList &caCertificatePaths,
diff --git a/src/manager-lib/intentaminterface.cpp b/src/manager-lib/intentaminterface.cpp
index d8bf21aa..52b8e5be 100644
--- a/src/manager-lib/intentaminterface.cpp
+++ b/src/manager-lib/intentaminterface.cpp
@@ -83,34 +83,30 @@ static QString sysUiId = qSL(":sysui:");
IntentServer *IntentAMImplementation::createIntentServerAndClientInstance(PackageManager *packageManager,
- const QMap<QString, int> &timeouts)
+ int disambiguationTimeout,
+ int startApplicationTimeout,
+ int replyFromApplicationTimeout,
+ int replyFromSystemTimeout)
{
auto intentServerAMInterface = new IntentServerAMImplementation;
auto intentClientAMInterface = new IntentClientAMImplementation(intentServerAMInterface);
auto intentServer = IntentServer::createInstance(intentServerAMInterface);
auto intentClient = IntentClient::createInstance(intentClientAMInterface);
- auto it = timeouts.constFind(qSL("disambiguation"));
- if (it != timeouts.cend())
- intentServer->setDisambiguationTimeout(it.value());
- it = timeouts.constFind(qSL("startApplication"));
- if (it != timeouts.cend())
- intentServer->setStartApplicationTimeout(it.value());
- it = timeouts.constFind(qSL("replyFromApplication"));
- if (it != timeouts.cend()) {
- int t = it.value();
-
- // These timeouts are for the same thing - the time needed for the application's handler to
- // generate a reply - but one is for the server side, while the other for the client side.
- // Having two separate config values would be confusing, so we set the application side to
- // 90% of the server side, because the communication overhead is not included there.
+ intentServer->setDisambiguationTimeout(disambiguationTimeout);
+ intentServer->setStartApplicationTimeout(startApplicationTimeout);
+
+ // These timeouts are for the same thing - the time needed for the application's handler to
+ // generate a reply - but one is for the server side, while the other for the client side.
+ // Having two separate config values would be confusing, so we set the application side to
+ // 90% of the server side, because the communication overhead is not included there.
+ {
+ int t = replyFromApplicationTimeout;
intentServer->setReplyFromApplicationTimeout(t);
intentClient->setReplyFromApplicationTimeout(t <= 0 ? t : int(t * 0.9));
}
- it = timeouts.constFind(qSL("requestToSystem"));
- if (it != timeouts.cend())
- intentClient->setReplyFromSystemTimeout(it.value());
+ intentClient->setReplyFromSystemTimeout(replyFromSystemTimeout);
// this way, deleting the server (the return value of this factory function) will get rid
// of both client and server as well as both their AM interfaces
diff --git a/src/manager-lib/intentaminterface.h b/src/manager-lib/intentaminterface.h
index 6363516e..49136b13 100644
--- a/src/manager-lib/intentaminterface.h
+++ b/src/manager-lib/intentaminterface.h
@@ -66,7 +66,9 @@ class PackageManager;
class IntentServerRequest;
namespace IntentAMImplementation {
-IntentServer *createIntentServerAndClientInstance(PackageManager *packageManager, const QMap<QString, int> &timeouts);
+IntentServer *createIntentServerAndClientInstance(PackageManager *packageManager, int disambiguationTimeout,
+ int startApplicationTimeout, int replyFromApplicationTimeout,
+ int replyFromSystemTimeout);
}
// the server side
diff --git a/src/package-lib/packageextractor.cpp b/src/package-lib/packageextractor.cpp
index a357076a..c3244c6f 100644
--- a/src/package-lib/packageextractor.cpp
+++ b/src/package-lib/packageextractor.cpp
@@ -465,7 +465,8 @@ void PackageExtractorPrivate::processMetaData(const QByteArray &metadata, QCrypt
}
try {
- checkYamlFormat(docs, -2 /*at least 2 docs*/, { isHeader ? "am-package-header" : "am-package-footer" }, 2);
+ checkYamlFormat(docs, -2 /*at least 2 docs*/, { { isHeader ? qSL("am-package-header")
+ : qSL("am-package-footer"), 2 } });
} catch (const Exception &e) {
throw Exception(Error::Package, "metadata has an invalid format specification: %1").arg(e.errorString());
}
diff --git a/src/tools/appman/appman.cpp b/src/tools/appman/appman.cpp
index 88865cc0..b607a7f3 100644
--- a/src/tools/appman/appman.cpp
+++ b/src/tools/appman/appman.cpp
@@ -47,7 +47,7 @@
#include "global.h"
#include "logging.h"
#include "main.h"
-#include "defaultconfiguration.h"
+#include "configuration.h"
#include "packageutilities.h"
#if !defined(AM_DISABLE_INSTALLER)
# include "sudo.h"
@@ -100,7 +100,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
bool onlyOnePositionalArgument = true;
#endif
- DefaultConfiguration cfg(additionalDescription, onlyOnePositionalArgument);
+ Configuration cfg(additionalDescription, onlyOnePositionalArgument);
cfg.parse(&deploymentWarnings);
StartupTimer::instance()->checkpoint("after command line parse");
diff --git a/tests/applicationinstaller/tst_applicationinstaller.cpp b/tests/applicationinstaller/tst_applicationinstaller.cpp
index 2ac84814..438b5929 100644
--- a/tests/applicationinstaller/tst_applicationinstaller.cpp
+++ b/tests/applicationinstaller/tst_applicationinstaller.cpp
@@ -395,7 +395,7 @@ void tst_PackageManager::packageInstallation_data()
<< false << false << false << false << nomd << "The package icon (as stated in info.yaml) must be the second file in the package. Expected 'icon.png', got 'test'";
QTest::newRow("invalid-header-format") \
<< "test-invalid-header-formatversion.appkg" << ""
- << false << false << false << false << nomd << "metadata has an invalid format specification: wrong formatVersion header: expected 2, got 0";
+ << false << false << false << false << nomd << "metadata has an invalid format specification: wrong header: expected am-package-header version 2, got am-package-header version 0";
QTest::newRow("invalid-header-diskspaceused") \
<< "test-invalid-header-diskspaceused.appkg" << ""
<< false << false << false << false << nomd << "metadata has an invalid diskSpaceUsed field (0)";
@@ -410,7 +410,7 @@ void tst_PackageManager::packageInstallation_data()
<< false << false << false << false << nomd << "~package digest mismatch.*";
QTest::newRow("invalid-info.yaml") \
<< "test-invalid-info.appkg" << ""
- << false << false << false << false << nomd << "~.*YAML parse error at line \\d+, column \\d+: did not find expected key";
+ << false << false << false << false << nomd << "~.*YAML parse error.*did not find expected key.*";
QTest::newRow("invalid-info.yaml-id") \
<< "test-invalid-info-id.appkg" << ""
<< false << false << false << false << nomd << "~.*the identifier \\(:invalid\\) is not a valid package-id: must consist of printable ASCII characters only, except any of .*";
diff --git a/tests/main/tst_main.cpp b/tests/main/tst_main.cpp
index f62054b5..f014a6b2 100644
--- a/tests/main/tst_main.cpp
+++ b/tests/main/tst_main.cpp
@@ -362,16 +362,13 @@ void tst_Main::mainQmlFile()
config = new DefaultConfiguration(QStringList(QFINDTESTDATA("am-config.yaml")), QString());
config->parseWithArguments(arguments);
- QString errorMsg;
-
try {
main->setup(config);
+ QVERIFY2(expectedErrorMsg.isEmpty(), "Exception was expected, but none was thrown");
} catch (const std::exception &e) {
- errorMsg.append(e.what());
+ QCOMPARE(e.what(), expectedErrorMsg);
}
- QCOMPARE(errorMsg, expectedErrorMsg);
-
delete config;
config = nullptr;
delete main;
diff --git a/tests/packager-tool/tst_packager-tool.cpp b/tests/packager-tool/tst_packager-tool.cpp
index bfd0d1d3..eaa31d30 100644
--- a/tests/packager-tool/tst_packager-tool.cpp
+++ b/tests/packager-tool/tst_packager-tool.cpp
@@ -168,11 +168,11 @@ void tst_PackagerTool::test()
// no valid destination
QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), pathTo("test.appkg")), errorString));
- QVERIFY2(errorString.contains(qL1S("not a directory")), qPrintable(errorString));
+ QVERIFY2(errorString.contains(qL1S("is not a directory")), qPrintable(errorString));
// no valid info.yaml
QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), errorString));
- QVERIFY2(errorString.contains(qL1S("could not open file for reading")), qPrintable(errorString));
+ QVERIFY2(errorString.contains(qL1S("Cannot open for reading")), qPrintable(errorString));
// add an info.yaml file
createInfoYaml(tmp);
@@ -279,10 +279,10 @@ void tst_PackagerTool::brokenMetadata_data()
QTest::addColumn<QVariant>("yamlValue");
QTest::addColumn<QString>("errorString");
- QTest::newRow("missing-name") << qSL("name") << QVariant() << "~.*the 'name' field must not be empty";
- QTest::newRow("missing-runtime") << qSL("runtime") << QVariant() << "~.*the 'runtimeName' field must not be empty.*";
- QTest::newRow("missing-identifier") << qSL("id") << QVariant() << "~.*packages need to have an id.*";
- QTest::newRow("missing-code") << qSL("code") << QVariant() << "~.*the 'code' field must not be empty.*";
+ QTest::newRow("missing-name") << qSL("name") << QVariant() << "~.*Required field\\(s\\) 'name' are missing.*";
+ QTest::newRow("missing-runtime") << qSL("runtime") << QVariant() << "~.*Required field\\(s\\) 'runtime' are missing.*";
+ QTest::newRow("missing-identifier") << qSL("id") << QVariant() << "~.*Required field\\(s\\) 'id' are missing.*";
+ QTest::newRow("missing-code") << qSL("code") << QVariant() << "~.*Required field\\(s\\) 'code' are missing.*";
}
void tst_PackagerTool::brokenMetadata()
@@ -360,7 +360,10 @@ bool tst_PackagerTool::createInfoYaml(QTemporaryDir &tmp, const QString &changeF
}
QVariantMap map = docs.at(1).toMap();
- map[changeField] = toValue;
+ if (!toValue.isValid())
+ map.remove(changeField);
+ else
+ map[changeField] = toValue;
yaml = QtYaml::yamlFromVariantDocuments({ docs.at(0), map });
}
diff --git a/tests/qml/configs/tst_configs.qml b/tests/qml/configs/tst_configs.qml
index 5acbeca1..7e92e89f 100644
--- a/tests/qml/configs/tst_configs.qml
+++ b/tests/qml/configs/tst_configs.qml
@@ -90,7 +90,7 @@ TestCase {
function test_basic_ipc() {
compare(NotificationManager.count, 0);
compare(windowAddedSpy.count, 0);
- ApplicationManager.startApplication("test.configs.app");
+ verify(ApplicationManager.startApplication("test.configs.app"))
windowAddedSpy.wait();
compare(windowAddedSpy.count, 1);
var window = windowAddedSpy.signalArguments[0][0];
diff --git a/tests/qml/intents/am-config.yaml b/tests/qml/intents/am-config.yaml
index 6c4957a1..1667c123 100644
--- a/tests/qml/intents/am-config.yaml
+++ b/tests/qml/intents/am-config.yaml
@@ -20,4 +20,4 @@ intents:
disambiguation: 1000
startApplication: 1000
replyFromApplication: 1000
- requestToSystem: 2000
+ replyFromSystem: 2000
diff --git a/tests/yaml/data/cache1.yaml b/tests/yaml/data/cache1.yaml
new file mode 100644
index 00000000..b3fcddb9
--- /dev/null
+++ b/tests/yaml/data/cache1.yaml
@@ -0,0 +1,13 @@
+name: cache1
+file: ${FILE}
+
+## content for a merge test
+#bool: true
+#list: [ 1, 2 ]
+#map:
+# key1: value1
+# key2: value2
+# key3:
+# key31: value31
+# key32: value32
+# key4: 4
diff --git a/tests/yaml/data/cache2.yaml b/tests/yaml/data/cache2.yaml
new file mode 100644
index 00000000..8ce7bae3
--- /dev/null
+++ b/tests/yaml/data/cache2.yaml
@@ -0,0 +1,14 @@
+name: cache2
+file: ${FILE}
+
+## content for a merge test
+#bool: false
+#list: [ 3, 4, 5 ]
+#map:
+# key1: value1
+# key2: not-value2
+# key3:
+# key31: [ 5, 6 ]
+# key32: not-value32
+# key33: true
+# key5: 5
diff --git a/tests/yaml/tst_yaml.cpp b/tests/yaml/tst_yaml.cpp
index b19c9818..1b53e533 100644
--- a/tests/yaml/tst_yaml.cpp
+++ b/tests/yaml/tst_yaml.cpp
@@ -5,7 +5,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:GPL-EXCEPT-QTAS$
** Commercial License Usage
@@ -32,6 +32,7 @@
#include <QtTest>
#include "qtyaml.h"
+#include "configcache.h"
#include "exception.h"
#include "global.h"
@@ -47,6 +48,8 @@ public:
private slots:
void parser();
void documentParser();
+ void cache();
+ void mergedCache();
};
@@ -55,6 +58,8 @@ tst_Yaml::tst_Yaml()
void tst_Yaml::parser()
{
+ static const QVariant vnull = QVariant::fromValue(nullptr);
+
QVector<QPair<const char *, QVariant>> tests = {
{ "dec", QVariant::fromValue<int>(10) },
{ "hex", QVariant::fromValue<int>(16) },
@@ -64,17 +69,17 @@ void tst_Yaml::parser()
{ "float2", QVariant::fromValue<double>(.1) },
{ "float3", QVariant::fromValue<double>(.1) },
{ "number-separators", QVariant::fromValue<int>(1234567) },
- { "bool-true", QVariant::fromValue<bool>(true) },
- { "bool-yes", QVariant::fromValue<bool>(true) },
- { "bool-false", QVariant::fromValue<bool>(false) },
- { "bool-no", QVariant::fromValue<bool>(false) },
- { "null-literal", QVariant() },
- { "null-tilde", QVariant() },
- { "string-unquoted", QVariant::fromValue<QString>(qSL("unquoted")) },
- { "string-singlequoted", QVariant::fromValue<QString>(qSL("singlequoted")) },
- { "string-doublequoted", QVariant::fromValue<QString>(qSL("doublequoted")) },
+ { "bool-true", true },
+ { "bool-yes", true },
+ { "bool-false", false },
+ { "bool-no", false },
+ { "null-literal", vnull },
+ { "null-tilde", vnull },
+ { "string-unquoted", QVariant::fromValue<QString>("unquoted") },
+ { "string-singlequoted", QVariant::fromValue<QString>("singlequoted") },
+ { "string-doublequoted", QVariant::fromValue<QString>("doublequoted") },
{ "list-int", QVariantList { 1, 2, 3 } },
- { "list-mixed", QVariantList { 1, qSL("two"), QVariantList { true, QVariant { } } } },
+ { "list-mixed", QVariantList { 1, "two", QVariantList { true, vnull } } },
{ "map1", QVariantMap { { "a", 1 }, { "b", "two" }, { "c", QVariantList { 1, 2, 3 } } } }
};
@@ -105,7 +110,7 @@ void tst_Yaml::parser()
case YamlParser::Scalar: {
QVERIFY(p->isScalar());
QVariant v = p->parseScalar();
- QCOMPARE(v.type(), value.type());
+ QCOMPARE(int(v.type()), int(value.type()));
QVERIFY(v == value);
break;
}
@@ -130,7 +135,7 @@ void tst_Yaml::parser()
QVERIFY(p->isScalar());
QVariant v = p->parseScalar();
QCOMPARE(v.type(), QVariant::String);
- QCOMPARE(v.toString(), qSL("ext string"));
+ QCOMPARE(v.toString(), "ext string");
} }
};
p->parseFields(extFields);
@@ -174,6 +179,8 @@ void tst_Yaml::parser()
void tst_Yaml::documentParser()
{
+ static const QVariant vnull = QVariant::fromValue(nullptr);
+
try {
QFile f(":/data/test.yaml");
QVERIFY2(f.open(QFile::ReadOnly), qPrintable(f.errorString()));
@@ -200,13 +207,13 @@ void tst_Yaml::documentParser()
{ "bool-yes", true },
{ "bool-false", false },
{ "bool-no", false },
- { "null-literal", QVariant() },
- { "null-tilde", QVariant() },
- { "string-unquoted", qSL("unquoted") },
- { "string-singlequoted", qSL("singlequoted") },
- { "string-doublequoted", qSL("doublequoted") },
+ { "null-literal", vnull },
+ { "null-tilde", vnull },
+ { "string-unquoted", "unquoted" },
+ { "string-singlequoted", "singlequoted" },
+ { "string-doublequoted", "doublequoted" },
{ "list-int", QVariantList { 1, 2, 3 } },
- { "list-mixed", QVariantList { 1, qSL("two"), QVariantList { true, QVariant { } } } },
+ { "list-mixed", QVariantList { 1, qSL("two"), QVariantList { true, vnull } } },
{ "map1", QVariantMap { { "a", 1 }, { "b", "two" }, { "c", QVariantList { 1, 2, 3 } } } },
@@ -227,6 +234,106 @@ void tst_Yaml::documentParser()
QVERIFY2(false, e.what());
}
}
+struct CacheTest
+{
+ QString name;
+ QString file;
+};
+
+// GCC < 7 bug, currently still in RHEL7, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
+// this should simply be:
+// template<> class QT_PREPEND_NAMESPACE_AM(ConfigCacheAdaptor<CacheTest>)
+
+QT_BEGIN_NAMESPACE_AM
+template<> class ConfigCacheAdaptor<CacheTest>
+{
+public:
+ CacheTest *loadFromSource(QIODevice *source, const QString &fileName)
+ {
+ QScopedPointer<CacheTest> ct(new CacheTest);
+ YamlParser p(source->readAll(), fileName);
+ p.nextDocument();
+ p.parseFields({ { "name", true, YamlParser::Scalar, [&ct](YamlParser *p) {
+ ct->name = p->parseScalar().toString(); } },
+ { "file", true, YamlParser::Scalar, [&ct](YamlParser *p) {
+ ct->file = p->parseScalar().toString(); } }
+ });
+ return ct.take();
+ }
+ CacheTest *loadFromCache(QDataStream &ds)
+ {
+ CacheTest *ct = new CacheTest;
+ ds >> ct->name >> ct->file;
+ return ct;
+ }
+ void saveToCache(QDataStream &ds, const CacheTest *ct)
+ {
+ ds << ct->name << ct->file;
+ }
+
+ void merge(CacheTest *ct1, const CacheTest *ct2)
+ {
+ ct1->name = ct2->name;
+ ct1->file = ct1->file + qSL(",") + ct2->file;
+ }
+ void preProcessSourceContent(QByteArray &sourceContent, const QString &fileName)
+ {
+ sourceContent.replace("${FILE}", fileName.toUtf8());
+ }
+ QStringList *warnings;
+};
+QT_END_NAMESPACE_AM
+
+void tst_Yaml::cache()
+{
+ QStringList files = { ":/data/cache1.yaml", ":/data/cache2.yaml" };
+ QStringList warnings;
+
+ for (int step = 0; step < 2; ++step) {
+ try {
+ ConfigCache<CacheTest> cache(files, "cache-test", step == 0 ? AbstractConfigCache::ClearCache
+ : AbstractConfigCache::None);
+ cache.parse(&warnings);
+ QVERIFY2(warnings.isEmpty(), qPrintable(warnings.join(qSL("\n"))));
+ CacheTest *ct1 = cache.takeResult(0);
+ QVERIFY(ct1);
+ QCOMPARE(ct1->name, "cache1");
+ QCOMPARE(ct1->file, ":/data/cache1.yaml");
+ CacheTest *ct2 = cache.takeResult(1);
+ QVERIFY(ct2);
+ QCOMPARE(ct2->name, "cache2");
+ QCOMPARE(ct2->file, ":/data/cache2.yaml");
+ } catch (const Exception &e) {
+ QVERIFY2(false, e.what());
+ }
+ }
+}
+
+void tst_Yaml::mergedCache()
+{
+ QStringList files = { ":/data/cache1.yaml", ":/data/cache2.yaml" };
+ QStringList warnings;
+
+ for (int step = 0; step < 4; ++step) {
+ AbstractConfigCache::Options options = AbstractConfigCache::MergedResult;
+ if (step % 2 == 0)
+ options |= AbstractConfigCache::ClearCache;
+ if (step == 2)
+ std::reverse(files.begin(), files.end());
+
+ try {
+ ConfigCache<CacheTest> cache(files, "cache-test", options);
+ cache.parse(&warnings);
+ QVERIFY2(warnings.isEmpty(), qPrintable(warnings.join(qSL("\n"))));
+ CacheTest *ct = cache.takeMergedResult();
+ QVERIFY(ct);
+ QCOMPARE(ct->name, QFileInfo(files.last()).baseName());
+ QCOMPARE(ct->file, files.join(qSL(",")));
+ } catch (const Exception &e) {
+ QVERIFY2(false, e.what());
+ }
+ }
+}
QTEST_MAIN(tst_Yaml)
diff --git a/tests/yaml/yaml.pro b/tests/yaml/yaml.pro
index 71961796..5cc4bbb5 100644
--- a/tests/yaml/yaml.pro
+++ b/tests/yaml/yaml.pro
@@ -6,4 +6,7 @@ QT *= appman_common-private
SOURCES += tst_yaml.cpp
-RESOURCES += data/test.yaml
+RESOURCES += \
+ data/test.yaml \
+ data/cache1.yaml \
+ data/cache2.yaml \
diff --git a/util/bash/appman-prompt b/util/bash/appman-prompt
index c1f37766..0538274e 100644
--- a/util/bash/appman-prompt
+++ b/util/bash/appman-prompt
@@ -32,10 +32,10 @@ _appman()
local cur opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
- opts="--app-image-mount-dir --build-config --builtin-apps-manifest-dir -c --clear-config-cache --config-file \
+ opts="--app-image-mount-dir --build-config --builtin-apps-manifest-dir -c --clear-cache --config-file \
--database --dbus --force-multi-process --force-single-process --fullscreen --help -I \
---installed-apps-manifest-dir --load-dummydata --logging-rule --no-config-cache --no-dlt-logging \
---no-fullscreen --no-security --no-ui-watchdog -o --option --qml-debug --recreate-database -r --single-app \
+--installed-apps-manifest-dir --load-dummydata --logging-rule --no-cache --no-dlt-logging \
+--no-fullscreen --no-security --no-ui-watchdog -o --option --qml-debug --single-app \
--slow-animations --verbose --version --wayland-socket-name"
if [[ ${cur} == -* ]] ; then