diff options
Diffstat (limited to 'src')
33 files changed, 641 insertions, 349 deletions
diff --git a/src/application-lib/installationreport.cpp b/src/application-lib/installationreport.cpp index b52d772c..099c757f 100644 --- a/src/application-lib/installationreport.cpp +++ b/src/application-lib/installationreport.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/ @@ -163,10 +164,10 @@ bool InstallationReport::isValid() const return PackageInfo::isValidApplicationId(m_packageId) && !m_digest.isEmpty() && !m_files.isEmpty(); } -bool InstallationReport::deserialize(QIODevice *from) +void InstallationReport::deserialize(QIODevice *from) { if (!from || !from->isReadable() || (from->size() > 2*1024*1024)) - return false; + throw Exception("Installation report is invalid"); m_digest.clear(); m_files.clear(); @@ -175,13 +176,9 @@ bool InstallationReport::deserialize(QIODevice *from) QVector<QVariant> docs = QtYaml::variantDocumentsFromYaml(from->readAll(), &error); if (error.error != QJsonParseError::NoError) - return false; + throw Exception("Failed to parse YAML: %1").arg(error.errorString()); - try { - checkYamlFormat(docs, 3 /*number of expected docs*/, { "am-installation-report" }, 3 /*version*/); - } catch (const Exception &) { - return false; - } + checkYamlFormat(docs, 3 /*number of expected docs*/, { "am-installation-report" }, 3 /*version*/); const QVariantMap &root = docs.at(1).toMap(); @@ -189,43 +186,44 @@ bool InstallationReport::deserialize(QIODevice *from) if (m_packageId.isEmpty()) { m_packageId = root[qSL("packageId")].toString(); if (m_packageId.isEmpty()) - throw false; + throw Exception("packageId is empty"); } else if (root[qSL("packageId")].toString() != m_packageId) { - throw false; + throw Exception("packageId does not match: expected '%1', but got '%2'") + .arg(m_packageId).arg(root[qSL("packageId")].toString()); } m_diskSpaceUsed = root[qSL("diskSpaceUsed")].toULongLong(); m_digest = QByteArray::fromHex(root[qSL("digest")].toString().toLatin1()); if (m_digest.isEmpty()) - throw false; + throw Exception("digest is empty"); auto devSig = root.find(qSL("developerSignature")); if (devSig != root.end()) { m_developerSignature = QByteArray::fromBase64(devSig.value().toString().toLatin1()); if (m_developerSignature.isEmpty()) - throw false; + throw Exception("developerSignature is empty"); } auto storeSig = root.find(qSL("storeSignature")); if (storeSig != root.end()) { m_storeSignature = QByteArray::fromBase64(storeSig.value().toString().toLatin1()); if (m_storeSignature.isEmpty()) - throw false; + throw Exception("storeSignature is empty"); } auto extra = root.find(qSL("extra")); if (extra != root.end()) { m_extraMetaData = extra.value().toMap(); if (m_extraMetaData.isEmpty()) - throw false; + throw Exception("extra metadata is empty"); } auto extraSigned = root.find(qSL("extraSigned")); if (extraSigned != root.end()) { m_extraSignedMetaData = extraSigned.value().toMap(); if (m_extraSignedMetaData.isEmpty()) - throw false; + throw Exception("extraSigned metadata is empty"); } m_files = root[qSL("files")].toStringList(); if (m_files.isEmpty()) - throw false; + throw Exception("No files"); // see if the file has been tampered with by checking the hmac QByteArray hmacFile = QByteArray::fromHex(docs[2].toMap().value(qSL("hmac")).toString().toLatin1()); @@ -235,16 +233,16 @@ bool InstallationReport::deserialize(QIODevice *from) hmacKey, QCryptographicHash::Sha256); - if (hmacFile != hmacCalc) - throw false; - - return true; - } catch (bool) { + if (hmacFile != hmacCalc) { + throw Exception("HMAC does not match: expected '%1', but got '%2'") + .arg(hmacCalc.toHex()).arg(hmacFile.toHex()); + } + } catch (const Exception &) { m_digest.clear(); m_diskSpaceUsed = 0; m_files.clear(); - return false; + throw; } } diff --git a/src/application-lib/installationreport.h b/src/application-lib/installationreport.h index 87fa56c1..aa01f658 100644 --- a/src/application-lib/installationreport.h +++ b/src/application-lib/installationreport.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/ @@ -83,7 +84,7 @@ public: bool isValid() const; - bool deserialize(QIODevice *from); + void deserialize(QIODevice *from); bool serialize(QIODevice *to) const; private: diff --git a/src/application-lib/intentinfo.cpp b/src/application-lib/intentinfo.cpp index ef53ce7e..8ce17a1d 100644 --- a/src/application-lib/intentinfo.cpp +++ b/src/application-lib/intentinfo.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -86,27 +87,27 @@ QStringList IntentInfo::categories() const QMap<QString, QString> IntentInfo::names() const { - return m_name; + return m_names.isEmpty() ? m_packageInfo->names() : m_names; } QString IntentInfo::name(const QString &language) const { - return m_name.value(language); + return m_names.isEmpty() ? m_packageInfo->name(language) : m_names.value(language); } QMap<QString, QString> IntentInfo::descriptions() const { - return m_description; + return m_descriptions; } QString IntentInfo::description(const QString &language) const { - return m_description.value(language); + return m_descriptions.value(language); } QString IntentInfo::icon() const { - return m_icon; + return m_icon.isEmpty() ? m_packageInfo->icon() : m_icon; } void IntentInfo::writeToDataStream(QDataStream &ds) const @@ -117,8 +118,8 @@ void IntentInfo::writeToDataStream(QDataStream &ds) const << m_parameterMatch << m_handlingApplicationId << m_categories - << m_name - << m_description + << m_names + << m_descriptions << m_icon; } @@ -133,8 +134,8 @@ IntentInfo *IntentInfo::readFromDataStream(PackageInfo *pkg, QDataStream &ds) >> intent->m_parameterMatch >> intent->m_handlingApplicationId >> intent->m_categories - >> intent->m_name - >> intent->m_description + >> intent->m_names + >> intent->m_descriptions >> intent->m_icon; intent->m_visibility = (visibilityStr == qSL("public")) ? Public : Private; diff --git a/src/application-lib/intentinfo.h b/src/application-lib/intentinfo.h index aee5b8ab..9834e42c 100644 --- a/src/application-lib/intentinfo.h +++ b/src/application-lib/intentinfo.h @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -93,8 +94,8 @@ private: QString m_handlingApplicationId; QStringList m_categories; - QMap<QString, QString> m_name; // language -> name - QMap<QString, QString> m_description; // language -> description + QMap<QString, QString> m_names; // language -> name + QMap<QString, QString> m_descriptions; // language -> description QString m_icon; // relative to info.json location friend class YamlPackageScanner; diff --git a/src/application-lib/packagedatabase.cpp b/src/application-lib/packagedatabase.cpp index 3496faa7..4c927569 100644 --- a/src/application-lib/packagedatabase.cpp +++ b/src/application-lib/packagedatabase.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -65,6 +66,12 @@ PackageDatabase::PackageDatabase(const QString &singlePackagePath) Q_ASSERT(!singlePackagePath.isEmpty()); } +PackageDatabase::~PackageDatabase() +{ + qDeleteAll(m_builtInPackages); + qDeleteAll(m_installedPackages); +} + QString PackageDatabase::installedPackagesDir() const { return m_installedPackagesDir; @@ -95,21 +102,21 @@ void PackageDatabase::saveToCache() //TODO: write cache file } -bool PackageDatabase::canBeRevertedToBuiltIn(PackageInfo *pi) +bool PackageDatabase::builtInHasRemovableUpdate(PackageInfo *packageInfo) const { - if (!pi || pi->isBuiltIn() || !m_installedPackages.contains(pi)) + if (!packageInfo || packageInfo->isBuiltIn() || !m_installedPackages.contains(packageInfo)) return false; - for (auto it = m_builtInPackages.cbegin(); it != m_builtInPackages.cend(); ++it) { - if (it.key()->id() == pi->id()) + for (const auto *pi : m_builtInPackages) { + if (pi->id() == packageInfo->id()) return true; } return false; } -QMap<PackageInfo *, QString> PackageDatabase::loadManifestsFromDir(YamlPackageScanner *yps, const QString &manifestDir, bool scanningBuiltInApps) +QVector<PackageInfo *> PackageDatabase::loadManifestsFromDir(const QString &manifestDir, bool scanningBuiltInApps) { - QMap<PackageInfo *, QString> result; + QVector<PackageInfo *> result; auto flags = scanningBuiltInApps ? QDir::Dirs | QDir::NoDotAndDotDot : QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks; @@ -137,8 +144,8 @@ QMap<PackageInfo *, QString> PackageDatabase::loadManifestsFromDir(YamlPackageSc if (!scanningBuiltInApps && !pkgDir.exists(qSL(".installation-report.yaml"))) throw Exception("found a non-built-in package without an installation report"); - QString manifestPath = pkgDir.absoluteFilePath(yps->metaDataFileName()); - QScopedPointer<PackageInfo> pkg(loadManifest(yps, manifestPath)); + 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" @@ -152,13 +159,16 @@ QMap<PackageInfo *, QString> PackageDatabase::loadManifestsFromDir(YamlPackageSc throw Exception(f, "failed to open the installation report"); QScopedPointer<InstallationReport> report(new InstallationReport(pkg->id())); - if (!report->deserialize(&f)) - throw Exception(f, "failed to deserialize the installation report"); + try { + report->deserialize(&f); + } catch (const Exception &e) { + throw Exception("Failed to deserialize the installation report %1: %2") + .arg(f.fileName()).arg(e.errorString()); + } pkg->setInstallationReport(report.take()); - pkg->setBaseDir(QDir(m_installedPackagesDir).filePath(pkg->id())); } - result.insert(pkg.take(), manifestPath); + result.append(pkg.take()); } catch (const Exception &e) { qCDebug(LogSystem) << "Ignoring package" << pkgDirName << ":" << e.what(); } @@ -166,17 +176,6 @@ QMap<PackageInfo *, QString> PackageDatabase::loadManifestsFromDir(YamlPackageSc return result; } -PackageInfo *PackageDatabase::loadManifest(YamlPackageScanner *yps, const QString &manifestPath) -{ - QScopedPointer<PackageInfo> pkg(yps->scan(manifestPath)); - Q_ASSERT(pkg); - - if (pkg->applications().isEmpty()) - throw Exception("package contains no applications"); - - return pkg.take(); -} - void PackageDatabase::parse() { if (m_parsed) @@ -188,34 +187,43 @@ void PackageDatabase::parse() return; } - YamlPackageScanner yps; - if (!m_singlePackagePath.isEmpty()) { try { - m_builtInPackages.insert(loadManifest(&yps, m_singlePackagePath), m_singlePackagePath); + m_builtInPackages.append(PackageInfo::fromManifest(m_singlePackagePath)); } catch (const Exception &e) { throw Exception("Failed to load manifest for package: %1").arg(e.errorString()); } } else { for (const QString &dir : m_builtInPackagesDirs) - m_builtInPackages.unite(loadManifestsFromDir(&yps, dir, true)); + m_builtInPackages.append(loadManifestsFromDir(dir, true)); if (!m_installedPackagesDir.isEmpty()) - m_installedPackages = loadManifestsFromDir(&yps, m_installedPackagesDir, false); + m_installedPackages = loadManifestsFromDir(m_installedPackagesDir, false); } if (m_saveToCache) saveToCache(); } +void PackageDatabase::addPackageInfo(PackageInfo *package) +{ + m_installedPackages.append(package); +} + +void PackageDatabase::removePackageInfo(PackageInfo *package) +{ + if (m_installedPackages.removeAll(package)) + delete package; +} + QVector<PackageInfo *> PackageDatabase::installedPackages() const { - return m_installedPackages.keys().toVector(); + return m_installedPackages; } QVector<PackageInfo *> PackageDatabase::builtInPackages() const { - return m_builtInPackages.keys().toVector(); + return m_builtInPackages; } QT_END_NAMESPACE_AM diff --git a/src/application-lib/packagedatabase.h b/src/application-lib/packagedatabase.h index ce1610db..7b6b3584 100644 --- a/src/application-lib/packagedatabase.h +++ b/src/application-lib/packagedatabase.h @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -50,7 +51,6 @@ QT_BEGIN_NAMESPACE_AM class PackageInfo; -class YamlPackageScanner; class PackageDatabase @@ -58,6 +58,7 @@ class PackageDatabase public: PackageDatabase(const QStringList &builtInPackagesDirs, const QString &installedPackagesDir = QString()); PackageDatabase(const QString &singlePackagePath); + ~PackageDatabase(); QString installedPackagesDir() const; @@ -69,14 +70,14 @@ public: QVector<PackageInfo *> builtInPackages() const; QVector<PackageInfo *> installedPackages() const; - //TODO: runtime installations - //void addPackage(PackageInfo *package); - //void removePackage(PackageInfo *package); - //void updatePackage(PackageInfo *oldPackage, PackageInfo *newPackage); + // runtime installations + void addPackageInfo(PackageInfo *package); + void removePackageInfo(PackageInfo *package); private: - PackageInfo *loadManifest(YamlPackageScanner *yps, const QString &manifestPath); - QMap<PackageInfo *, QString> loadManifestsFromDir(YamlPackageScanner *yps, const QString &manifestDir, bool scanningBuiltInApps); + Q_DISABLE_COPY(PackageDatabase) + + QVector<PackageInfo *> loadManifestsFromDir(const QString &manifestDir, bool scanningBuiltInApps); bool loadFromCache(); void saveToCache(); @@ -88,10 +89,10 @@ private: QString m_installedPackagesDir; QString m_singlePackagePath; - QMap<PackageInfo *, QString> m_builtInPackages; - QMap<PackageInfo *, QString> m_installedPackages; + QVector<PackageInfo *> m_builtInPackages; + QVector<PackageInfo *> m_installedPackages; - bool canBeRevertedToBuiltIn(PackageInfo *pi); + bool builtInHasRemovableUpdate(PackageInfo *packageInfo) const; }; QT_END_NAMESPACE_AM diff --git a/src/application-lib/packageinfo.cpp b/src/application-lib/packageinfo.cpp index ee306431..32ac528a 100644 --- a/src/application-lib/packageinfo.cpp +++ b/src/application-lib/packageinfo.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -47,6 +48,7 @@ #include "intentinfo.h" #include "exception.h" #include "installationreport.h" +#include "yamlpackagescanner.h" QT_BEGIN_NAMESPACE_AM @@ -55,7 +57,10 @@ PackageInfo::PackageInfo() { } PackageInfo::~PackageInfo() -{ } +{ + qDeleteAll(m_intents); + qDeleteAll(m_applications); +} void PackageInfo::validate() const Q_DECL_NOEXCEPT_EXPR(false) { @@ -63,11 +68,14 @@ void PackageInfo::validate() const Q_DECL_NOEXCEPT_EXPR(false) if (!isValidApplicationId(id(), &errorMsg)) throw Exception(Error::Parse, "the identifier (%1) is not a valid package-id: %2").arg(id()).arg(errorMsg); + if (m_applications.isEmpty()) + throw Exception(Error::Parse, "package contains no applications"); + for (const auto &app : m_applications) { if (!isValidApplicationId(app->id(), &errorMsg)) throw Exception(Error::Parse, "the identifier (%1) is not a valid application-id: %2").arg(app->id()).arg(errorMsg); - if (app->absoluteCodeFilePath().isEmpty()) + if (app->codeFilePath().isEmpty()) throw Exception(Error::Parse, "the 'code' field must not be empty on application %1").arg(app->id()); if (app->runtimeName().isEmpty()) @@ -82,22 +90,22 @@ QString PackageInfo::id() const QMap<QString, QString> PackageInfo::names() const { - return m_name; + return m_names; } QString PackageInfo::name(const QString &language) const { - return m_name.value(language); + return m_names.value(language); } QMap<QString, QString> PackageInfo::descriptions() const { - return m_description; + return m_descriptions; } QString PackageInfo::description(const QString &language) const { - return m_description.value(language); + return m_descriptions.value(language); } QString PackageInfo::icon() const @@ -131,6 +139,7 @@ QVariantMap PackageInfo::dltConfiguration() const } const QDir &PackageInfo::baseDir() const + { return m_baseDir; } @@ -171,9 +180,9 @@ void PackageInfo::writeToDataStream(QDataStream &ds) const } ds << m_id - << m_name + << m_names << m_icon - << m_description + << m_descriptions << m_categories << m_version << m_builtIn @@ -199,9 +208,9 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds) QByteArray installationReport; ds >> pkg->m_id - >> pkg->m_name + >> pkg->m_names >> pkg->m_icon - >> pkg->m_description + >> pkg->m_descriptions >> pkg->m_categories >> pkg->m_version >> pkg->m_builtIn @@ -216,8 +225,11 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds) QBuffer buffer(&installationReport); buffer.open(QBuffer::ReadOnly); pkg->m_installationReport.reset(new InstallationReport(pkg->id())); - if (!pkg->m_installationReport->deserialize(&buffer)) + try { + pkg->m_installationReport->deserialize(&buffer); + } catch (...) { pkg->m_installationReport.reset(); + } } return pkg.take(); @@ -279,5 +291,14 @@ bool PackageInfo::isValidIcon(const QString &icon, QString *errorString) } } +QString PackageInfo::manifestPath() const +{ + return m_baseDir.filePath(m_manifestName); +} + +PackageInfo *PackageInfo::fromManifest(const QString &manifestPath) +{ + return YamlPackageScanner().scan(manifestPath); +} QT_END_NAMESPACE_AM diff --git a/src/application-lib/packageinfo.h b/src/application-lib/packageinfo.h index 777526f6..dc1708e1 100644 --- a/src/application-lib/packageinfo.h +++ b/src/application-lib/packageinfo.h @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2019 Luxoft Sweden AB ** Contact: https://www.qt.io/licensing/ ** @@ -52,6 +53,8 @@ QT_FORWARD_DECLARE_CLASS(QDataStream) +class tst_Application; + QT_BEGIN_NAMESPACE_AM class InstallationReport; @@ -62,7 +65,6 @@ class YamlPackageScanner; class PackageInfo { public: - PackageInfo(); ~PackageInfo(); void validate() const Q_DECL_NOEXCEPT_EXPR(false); @@ -97,10 +99,17 @@ public: static bool isValidApplicationId(const QString &appId, QString *errorString = nullptr); static bool isValidIcon(const QString &icon, QString *errorString = nullptr); + QString manifestPath() const; + + static PackageInfo *fromManifest(const QString &manifestPath); + private: + PackageInfo(); + + QString m_manifestName; QString m_id; - QMap<QString, QString> m_name; // language -> name - QMap<QString, QString> m_description; // language -> description + QMap<QString, QString> m_names; // language -> name + QMap<QString, QString> m_descriptions; // language -> description QStringList m_categories; QString m_icon; // relative to info.json location QString m_version; diff --git a/src/application-lib/packagescanner.h b/src/application-lib/packagescanner.h index 55e9ff3a..9e5956d9 100644 --- a/src/application-lib/packagescanner.h +++ b/src/application-lib/packagescanner.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/ @@ -55,8 +56,6 @@ public: virtual PackageInfo *scan(const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false) = 0; - virtual QString metaDataFileName() const = 0; - protected: PackageScanner() = default; diff --git a/src/application-lib/yamlpackagescanner.cpp b/src/application-lib/yamlpackagescanner.cpp index 9adbd627..54b937bc 100644 --- a/src/application-lib/yamlpackagescanner.cpp +++ b/src/application-lib/yamlpackagescanner.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/ @@ -115,7 +116,11 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E QStringList appIds; // duplicate check QScopedPointer<PackageInfo> pkgInfo(new PackageInfo); - pkgInfo->setBaseDir(QFileInfo(f).absoluteDir()); + { + QFileInfo fi(f); + pkgInfo->m_baseDir = fi.absoluteDir(); + pkgInfo->m_manifestName = fi.fileName(); + } QScopedPointer<ApplicationInfo> legacyAppInfo(legacy ? new ApplicationInfo(pkgInfo.data()) : nullptr); @@ -136,16 +141,16 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E fields.emplace_back("name", true, [&pkgInfo](const QVariant &v) { auto nameMap = v.toMap(); for (auto it = nameMap.constBegin(); it != nameMap.constEnd(); ++it) - pkgInfo->m_name.insert(it.key(), it.value().toString()); + pkgInfo->m_names.insert(it.key(), it.value().toString()); - if (pkgInfo->m_name.isEmpty()) + if (pkgInfo->m_names.isEmpty()) throw Exception(Error::Parse, "the 'name' field must not be empty"); }); if (!legacy) { fields.emplace_back("description", false, [&pkgInfo](const QVariant &v) { auto descriptionMap = v.toMap(); for (auto it = descriptionMap.constBegin(); it != descriptionMap.constEnd(); ++it) - pkgInfo->m_description.insert(it.key(), it.value().toString()); + pkgInfo->m_descriptions.insert(it.key(), it.value().toString()); }); } fields.emplace_back("categories", false, [&pkgInfo](const QVariant &v) { @@ -233,9 +238,9 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E appFields.emplace_back("id", true, [&appInfo, &appIds](const QVariant &v) { QString id = v.toString(); if (id.isEmpty()) - throw Exception(Error::Intents, "applications need to have an id"); + throw Exception(Error::Parse, "applications need to have an id"); if (appIds.contains(id)) - throw Exception(Error::Intents, "found two applications with id %1").arg(id); + throw Exception(Error::Parse, "found two applications with the same id %1").arg(id); appInfo->m_id = id; }); appFields.emplace_back("code", true, [&appInfo](const QVariant &v) { @@ -281,6 +286,7 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E }); parseMap(appsIt->toMap(), appFields); + appIds << appInfo->id(); pkgInfo->m_applications << appInfo.take(); } }); @@ -313,23 +319,13 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E .arg(intentInfo->m_id).arg(visibilityStr); } }); - intentFields.emplace_back(legacy ? "handledBy" : "handlingApplicationId", legacy ? false : true, [&pkgInfo, &intentInfo, &appIds](const QVariant &v) { + intentFields.emplace_back(legacy ? "handledBy" : "handlingApplicationId", false, [&intentInfo, &appIds](const QVariant &v) { QString appId = v.toString(); - - if (appId.isEmpty()) { - if (pkgInfo->m_applications.count() == 1) { - intentInfo->m_handlingApplicationId = pkgInfo->m_applications.constFirst()->id(); - } else { - throw Exception(Error::Intents, "a 'handlingApplicationId' field on intent %1 is needed if more than one application is defined") - .arg(intentInfo->m_id); - } + if (appIds.contains(appId)) { + intentInfo->m_handlingApplicationId = appId; } else { - if (appIds.contains(appId)) { - intentInfo->m_handlingApplicationId = appId; - } else { - throw Exception(Error::Intents, "the 'handlingApplicationId' field on intent %1 points to the unknown application id %2") - .arg(intentInfo->m_id).arg(appId); - } + throw Exception(Error::Intents, "the 'handlingApplicationId' field on intent %1 points to the unknown application id %2") + .arg(intentInfo->m_id).arg(appId); } }); intentFields.emplace_back("requiredCapabilities", false, [&intentInfo](const QVariant &v) { @@ -344,12 +340,12 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E intentFields.emplace_back("name", false, [&intentInfo](const QVariant &v) { auto nameMap = v.toMap(); for (auto it = nameMap.constBegin(); it != nameMap.constEnd(); ++it) - intentInfo->m_name.insert(it.key(), it.value().toString()); + intentInfo->m_names.insert(it.key(), it.value().toString()); }); intentFields.emplace_back("description", false, [&intentInfo](const QVariant &v) { auto descriptionMap = v.toMap(); for (auto it = descriptionMap.constBegin(); it != descriptionMap.constEnd(); ++it) - intentInfo->m_description.insert(it.key(), it.value().toString()); + intentInfo->m_descriptions.insert(it.key(), it.value().toString()); }); intentFields.emplace_back("categories", false, [&intentInfo](const QVariant &v) { intentInfo->m_categories = variantToStringList(v); @@ -357,6 +353,18 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E }); parseMap(intentsIt->toMap(), intentFields); + + if (intentInfo->handlingApplicationId().isEmpty()) { + if (legacy) { + intentInfo->m_handlingApplicationId = pkgInfo->id(); + } else if (pkgInfo->m_applications.count() == 1) { + intentInfo->m_handlingApplicationId = pkgInfo->m_applications.constFirst()->id(); + } else { + throw Exception(Error::Intents, "a 'handlingApplicationId' field on intent %1 is needed if more than one application is defined") + .arg(intentInfo->m_id); + } + } + pkgInfo->m_intents << intentInfo.take(); } }); @@ -374,10 +382,5 @@ PackageInfo *YamlPackageScanner::scan(const QString &filePath) Q_DECL_NOEXCEPT_E } } -QString YamlPackageScanner::metaDataFileName() const -{ - return qSL("info.yaml"); -} - QT_END_NAMESPACE_AM diff --git a/src/application-lib/yamlpackagescanner.h b/src/application-lib/yamlpackagescanner.h index d8eb4e22..cbd8cfcd 100644 --- a/src/application-lib/yamlpackagescanner.h +++ b/src/application-lib/yamlpackagescanner.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/ @@ -53,7 +54,6 @@ public: YamlPackageScanner(); PackageInfo *scan(const QString &filePath) Q_DECL_NOEXCEPT_EXPR(false) override; - QString metaDataFileName() const override; }; QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intent.cpp b/src/intent-server-lib/intent.cpp index 4ee207a8..7115050c 100644 --- a/src/intent-server-lib/intent.cpp +++ b/src/intent-server-lib/intent.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/ @@ -106,15 +107,6 @@ QT_BEGIN_NAMESPACE_AM Intent::Intent() { } -Intent::Intent(const Intent &other) - : m_intentId(other.m_intentId) - , m_visibility(other.m_visibility) - , m_requiredCapabilities(other.m_requiredCapabilities) - , m_parameterMatch(other.m_parameterMatch) - , m_applicationId(other.m_applicationId) - , m_backgroundHandlerId(other.m_backgroundHandlerId) -{ } - Intent::Intent(const QString &id, const QString &applicationId, const QString &backgroundHandlerId, const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch) : m_intentId(id) diff --git a/src/intent-server-lib/intent.h b/src/intent-server-lib/intent.h index 10f1c8c8..a8d3996e 100644 --- a/src/intent-server-lib/intent.h +++ b/src/intent-server-lib/intent.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/ @@ -69,7 +70,6 @@ public: Q_ENUM(Visibility) Intent(); - Intent(const Intent &other); QString intentId() const; Visibility visibility() const; diff --git a/src/main-lib/applicationinstaller.h b/src/main-lib/applicationinstaller.h index 978f6ce1..f83ddadb 100644 --- a/src/main-lib/applicationinstaller.h +++ b/src/main-lib/applicationinstaller.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/ @@ -95,7 +96,7 @@ public: Q_SCRIPTABLE QString installationLocationIdFromApplication(const QString &applicationId) const { auto app = ApplicationManager::instance()->fromId(applicationId); - if (app && ((!app->package()->isBuiltIn() || app->package()->canBeRevertedToBuiltIn()))) + if (app && ((!app->package()->isBuiltIn() || app->package()->builtInHasRemovableUpdate()))) return qL1S("internal-0"); return QString(); } diff --git a/src/main-lib/main.cpp b/src/main-lib/main.cpp index 1dcfa10f..ce5ff211 100644 --- a/src/main-lib/main.cpp +++ b/src/main-lib/main.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/ @@ -94,12 +95,8 @@ #include "logging.h" #include "main.h" #include "defaultconfiguration.h" -#include "applicationinfo.h" -#include "intentinfo.h" -#include "packageinfo.h" #include "applicationmanager.h" #include "packagemanager.h" -#include "package.h" #include "packagedatabase.h" #include "installationreport.h" #include "yamlpackagescanner.h" @@ -245,6 +242,11 @@ void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentW setupSingletons(cfg->containerSelectionConfiguration(), cfg->quickLaunchRuntimesPerContainer(), cfg->quickLaunchIdleLoad()); + if (!cfg->disableIntents()) + setupIntents(cfg->intentTimeouts()); + + registerPackages(); + if (m_installationDir.isEmpty() || cfg->disableInstaller()) { StartupTimer::instance()->checkpoint("skipping installer"); } else { @@ -252,9 +254,6 @@ void Main::setup(const DefaultConfiguration *cfg, const QStringList &deploymentW std::bind(&DefaultConfiguration::applicationUserIdSeparation, cfg, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } - if (!cfg->disableIntents()) - setupIntents(cfg->intentTimeouts()); - setLibraryPaths(libraryPaths() + cfg->pluginPaths()); setupQmlEngine(cfg->importPaths(), cfg->style()); setupWindowTitle(QString(), cfg->windowIcon()); @@ -459,30 +458,9 @@ void Main::loadPackageDatabase(bool recreateDatabase, const QString &singlePacka void Main::setupIntents(const QMap<QString, int> &timeouts) Q_DECL_NOEXCEPT_EXPR(false) { - m_intentServer = IntentAMImplementation::createIntentServerAndClientInstance(timeouts); - - qCDebug(LogSystem) << "Registering intents:"; - - const auto packages = m_packageManager->packages(); - for (const Package *package : packages) { - const auto intents = package->info()->intents(); - if (!intents.isEmpty()) - m_intentServer->addApplication(package->id()); - - for (const IntentInfo *intent : intents) { - if (!m_intentServer->addIntent(intent->id(), package->id(), intent->handlingApplicationId(), - intent->requiredCapabilities(), - intent->visibility() == IntentInfo::Public ? Intent::Public - : Intent::Private, - intent->parameterMatch())) { - throw Exception(Error::Intents, "could not add intent %1 for package %2") - .arg(intent->id()).arg(package->id()); - } - qCDebug(LogSystem).nospace().noquote() << " * " << intent->id() << " [package: " << package->id() << "]"; - } - } - - StartupTimer::instance()->checkpoint("after Intents setup"); + m_intentServer = IntentAMImplementation::createIntentServerAndClientInstance(m_packageManager, + timeouts); + StartupTimer::instance()->checkpoint("after IntentServer instantiation"); } void Main::setupSingletons(const QList<QPair<QString, QString>> &containerSelectionConfiguration, @@ -490,28 +468,17 @@ void Main::setupSingletons(const QList<QPair<QString, QString>> &containerSelect qreal quickLaunchIdleLoad) Q_DECL_NOEXCEPT_EXPR(false) { m_packageManager = PackageManager::createInstance(m_packageDatabase, m_documentDir); - - qCDebug(LogSystem) << "Registering packages:"; - - QVector<Application *> applications; - const auto allPackages = m_packageManager->packages(); - for (auto package : allPackages) { - qCDebug(LogSystem).nospace().noquote() << " * " << package->id() << " [at: " - << QDir().relativeFilePath(package->info()->baseDir().path()) << "]"; - const auto appInfos = package->info()->applications(); - for (auto appInfo : appInfos) { - applications << new Application(appInfo, package); - qCDebug(LogSystem).nospace().noquote() << " * application:" << appInfo->id(); - } - } - m_applicationManager = ApplicationManager::createInstance(m_isSingleProcessMode); - m_applicationManager->setApplications(applications); - connect(&m_applicationManager->internalSignals, &ApplicationManagerInternalSignals::applicationsChanged, - this, [this]() { - //m_packageDatabase->saveToCache(); - //TODO: this is wrong - we haven't update the info cache! + connect(&m_packageManager->internalSignals, &PackageManagerInternalSignals::registerApplication, + m_applicationManager, [this](ApplicationInfo *applicationInfo, Package *package) { + m_applicationManager->addApplication(applicationInfo, package); + qCDebug(LogSystem).nospace().noquote() << " ++ application: " << applicationInfo->id() << " [package: " << package->id() << "]"; + }); + connect(&m_packageManager->internalSignals, &PackageManagerInternalSignals::unregisterApplication, + m_applicationManager, [this](ApplicationInfo *applicationInfo, Package *package) { + m_applicationManager->removeApplication(applicationInfo, package); + qCDebug(LogSystem).nospace().noquote() << " -- application: " << applicationInfo->id() << " [package: " << package->id() << "]"; }); if (m_noSecurity) @@ -590,13 +557,19 @@ void Main::setupInstaller(const QStringList &caCertificatePaths, //TODO: this could be delayed, but needs to have a lock on the app-db in this case m_packageManager->cleanupBrokenInstallations(); - StartupTimer::instance()->checkpoint("after PackageManager instantiation"); + StartupTimer::instance()->checkpoint("after installer setup"); #else Q_UNUSED(caCertificatePaths) Q_UNUSED(userIdSeparation) #endif // AM_DISABLE_INSTALLER } +void Main::registerPackages() +{ + m_packageManager->registerPackages(); + StartupTimer::instance()->checkpoint("after package registration"); +} + void Main::setupQmlEngine(const QStringList &importPaths, const QString &quickControlsStyle) { if (!quickControlsStyle.isEmpty()) diff --git a/src/main-lib/main.h b/src/main-lib/main.h index 703ee7ca..084c6747 100644 --- a/src/main-lib/main.h +++ b/src/main-lib/main.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/ @@ -121,6 +122,7 @@ protected: int quickLaunchRuntimesPerContainer, qreal quickLaunchIdleLoad) Q_DECL_NOEXCEPT_EXPR(false); void setupInstaller(const QStringList &caCertificatePaths, const std::function<bool(uint *, uint *, uint *)> &userIdSeparation) Q_DECL_NOEXCEPT_EXPR(false); + void registerPackages(); void setupQmlEngine(const QStringList &importPaths, const QString &quickControlsStyle = QString()); void setupWindowTitle(const QString &title, const QString &iconPath); diff --git a/src/manager-lib/application.cpp b/src/manager-lib/application.cpp index 432cc86e..f1a83d10 100644 --- a/src/manager-lib/application.cpp +++ b/src/manager-lib/application.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/ @@ -284,6 +285,7 @@ Application::Application(ApplicationInfo *info, Package *package) // handle package blocking: all apps have to be stopped and the stop state has to be reported // back to the package + connect(package, &Package::blockedChanged, this, [this](bool blocked) { emit blockedChanged(blocked); if (blocked && (runState() == Am::NotRunning)) @@ -295,6 +297,11 @@ Application::Application(ApplicationInfo *info, Package *package) if (isBlocked() && (runState == Am::NotRunning)) this->package()->applicationStoppedDueToBlock(id()); }); + + connect(package, &Package::stateChanged, this, [this]() { + emit stateChanged(state()); + }); + connect(package, &Package::bulkChange, this, &Application::bulkChange); } bool Application::start(const QString &documentUrl) @@ -321,7 +328,7 @@ void Application::stop(bool forceKill) ApplicationInfo *Application::info() const { - return m_info.data(); + return m_info; } PackageInfo *Application::packageInfo() const diff --git a/src/manager-lib/application.h b/src/manager-lib/application.h index f7ddeae5..46616b1e 100644 --- a/src/manager-lib/application.h +++ b/src/manager-lib/application.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/ @@ -73,25 +74,27 @@ class Application : public QObject Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/ApplicationObject 2.0 UNCREATABLE") Q_PROPERTY(QString id READ id CONSTANT) - Q_PROPERTY(QString runtimeName READ runtimeName NOTIFY bulkChange) - Q_PROPERTY(QVariantMap runtimeParameters READ runtimeParameters NOTIFY bulkChange) + Q_PROPERTY(QString runtimeName READ runtimeName CONSTANT) + Q_PROPERTY(QVariantMap runtimeParameters READ runtimeParameters CONSTANT) Q_PROPERTY(QUrl icon READ icon NOTIFY bulkChange) - Q_PROPERTY(QString documentUrl READ documentUrl NOTIFY bulkChange) // REMOVE - Q_PROPERTY(bool builtIn READ isBuiltIn NOTIFY bulkChange) + Q_PROPERTY(QString documentUrl READ documentUrl CONSTANT) // REMOVE + Q_PROPERTY(bool builtIn READ isBuiltIn CONSTANT) Q_PROPERTY(bool alias READ isAlias CONSTANT) // REMOVE Q_PROPERTY(Application *nonAliased READ nonAliased CONSTANT) // REMOVE - Q_PROPERTY(QStringList capabilities READ capabilities NOTIFY bulkChange) - Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes NOTIFY bulkChange) // REMOVE - Q_PROPERTY(QStringList categories READ categories NOTIFY bulkChange) - Q_PROPERTY(QVariantMap applicationProperties READ applicationProperties NOTIFY bulkChange) + Q_PROPERTY(QStringList capabilities READ capabilities CONSTANT) + Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes CONSTANT) // REMOVE + Q_PROPERTY(QStringList categories READ categories CONSTANT) + Q_PROPERTY(QVariantMap applicationProperties READ applicationProperties CONSTANT) Q_PROPERTY(AbstractRuntime *runtime READ currentRuntime NOTIFY runtimeChanged) Q_PROPERTY(int lastExitCode READ lastExitCode NOTIFY lastExitCodeChanged) Q_PROPERTY(QT_PREPEND_NAMESPACE_AM(Am::ExitStatus) lastExitStatus READ lastExitStatus NOTIFY lastExitStatusChanged) - Q_PROPERTY(QString version READ version NOTIFY bulkChange) - Q_PROPERTY(bool supportsApplicationInterface READ supportsApplicationInterface NOTIFY bulkChange) + Q_PROPERTY(QString version READ version CONSTANT) + Q_PROPERTY(bool supportsApplicationInterface READ supportsApplicationInterface CONSTANT) Q_PROPERTY(QString codeDir READ codeDir NOTIFY bulkChange) - Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(QT_PREPEND_NAMESPACE_AM(Am::RunState) runState READ runState NOTIFY runStateChanged) + + // legacy, forwarded to Package + Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(bool blocked READ isBlocked NOTIFY blockedChanged) public: @@ -163,7 +166,7 @@ signals: private: void setLastExitCodeAndStatus(int exitCode, Am::ExitStatus exitStatus); - QScopedPointer<ApplicationInfo> m_info; + ApplicationInfo *m_info = nullptr; Package *m_package = nullptr; AbstractRuntime *m_runtime = nullptr; diff --git a/src/manager-lib/applicationmanager.cpp b/src/manager-lib/applicationmanager.cpp index 523211f3..c5cea56d 100644 --- a/src/manager-lib/applicationmanager.cpp +++ b/src/manager-lib/applicationmanager.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/ @@ -1365,16 +1366,18 @@ Am::RunState ApplicationManager::applicationRunState(const QString &id) const return (index < 0) ? Am::NotRunning : d->apps.at(index)->runState(); } -void ApplicationManager::setApplications(const QVector<Application *> &apps) +void ApplicationManager::addApplication(ApplicationInfo *appInfo, Package *package) { - Q_ASSERT(d->apps.count() == 0); - for (auto app : apps) - addApplication(app); - registerMimeTypes(); -} + // check for id clashes outside of the package (the scanner made sure the package itself is + // consistent and doesn't have duplicates already) + for (Application *checkApp : qAsConst(d->apps)) { + if ((checkApp->id() == appInfo->id()) && (checkApp->package() != package)) { + throw Exception("found an application with the same id in package %1") + .arg(checkApp->packageInfo()->id()); + } + } -void ApplicationManager::addApplication(Application *app) -{ + auto app = new Application(appInfo, package); QQmlEngine::setObjectOwnership(app, QQmlEngine::CppOwnership); app->requests.startRequested = [this, app](const QString &documentUrl) { @@ -1393,8 +1396,45 @@ void ApplicationManager::addApplication(Application *app) this, [this, app]() { emitDataChanged(app, QVector<int> { IsBlocked }); }); + connect(app, &Application::bulkChange, + this, [this, app]() { + emitDataChanged(app); + }); + beginInsertRows(QModelIndex(), d->apps.count(), d->apps.count()); d->apps << app; + + endInsertRows(); + + registerMimeTypes(); + emit applicationAdded(appInfo->id()); +} + +void ApplicationManager::removeApplication(ApplicationInfo *appInfo, Package *package) +{ + int index = -1; + + for (int i = 0; i < d->apps.size(); ++i) { + if (d->apps.at(i)->info() == appInfo) { + index = i; + break; + } + } + if (index < 0) + return; + + Q_ASSERT(d->apps.at(index)->package() == package); + + emit applicationAboutToBeRemoved(appInfo->id()); + + beginRemoveRows(QModelIndex(), index, index); + auto app = d->apps.takeAt(index); + + endRemoveRows(); + + registerMimeTypes(); + + delete app; } QT_END_NAMESPACE_AM diff --git a/src/manager-lib/applicationmanager.h b/src/manager-lib/applicationmanager.h index 5513f119..9835d126 100644 --- a/src/manager-lib/applicationmanager.h +++ b/src/manager-lib/applicationmanager.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/ @@ -65,8 +66,6 @@ class ApplicationManagerInternalSignals : public QObject { Q_OBJECT signals: - // Emitted after an application is installed, updated, downgraded or removed - void applicationsChanged(); // Emitted every time a new Runtime object is created void newRuntimeCreated(QT_PREPEND_NAMESPACE_AM(AbstractRuntime) *runtime); }; @@ -98,11 +97,8 @@ public: QVariantMap systemProperties() const; void setSystemProperties(const QVariantMap &map); - // Set the initial application list - // To be used only during startup (ie, before exposing ApplicationManager to QML) as - // no model update signals are emitted. - void setApplications(const QVector<Application *> &apps); - + void addApplication(ApplicationInfo *appInfo, Package *package); + void removeApplication(ApplicationInfo *appInfo, Package *package); QVector<Application *> applications() const; Application *fromId(const QString &id) const; @@ -182,7 +178,6 @@ signals: private slots: void openUrlRelay(const QUrl &url); - void addApplication(Application *app); private: void emitDataChanged(Application *app, const QVector<int> &roles = QVector<int>()); diff --git a/src/manager-lib/applicationmanager_p.h b/src/manager-lib/applicationmanager_p.h index ad0ef7df..5ba28b3d 100644 --- a/src/manager-lib/applicationmanager_p.h +++ b/src/manager-lib/applicationmanager_p.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/ @@ -60,7 +61,6 @@ public: bool windowManagerCompositorReady = false; QVariantMap systemProperties; - QVector<PackageInfo> packages; QVector<Application *> apps; QString currentLocale; diff --git a/src/manager-lib/installationtask.cpp b/src/manager-lib/installationtask.cpp index 30cbc74a..ef907eb5 100644 --- a/src/manager-lib/installationtask.cpp +++ b/src/manager-lib/installationtask.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/ @@ -47,7 +48,6 @@ #include "packagemanager_p.h" #include "packageinfo.h" #include "packageextractor.h" -#include "yamlpackagescanner.h" #include "exception.h" #include "packagemanager.h" #include "sudo.h" @@ -285,8 +285,7 @@ void InstallationTask::checkExtractedFile(const QString &file) Q_DECL_NOEXCEPT_E throw Exception(Error::Package, "info.yaml must be the first file in the package. Got %1") .arg(file); - YamlPackageScanner yps; - m_package.reset(yps.scan(m_extractor->destinationDirectory().absoluteFilePath(file))); + m_package.reset(PackageInfo::fromManifest(m_extractor->destinationDirectory().absoluteFilePath(file))); if (m_package->id() != m_extractor->installationReport().packageId()) throw Exception(Error::Package, "the package identifiers in --PACKAGE-HEADER--' and info.yaml do not match"); diff --git a/src/manager-lib/intentaminterface.cpp b/src/manager-lib/intentaminterface.cpp index f787226a..fe1ea01c 100644 --- a/src/manager-lib/intentaminterface.cpp +++ b/src/manager-lib/intentaminterface.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/ @@ -68,6 +69,8 @@ #include "qmlinprocessruntime.h" #include "application.h" #include "applicationmanager.h" +#include "package.h" +#include "packagemanager.h" #include "applicationinfo.h" QT_BEGIN_NAMESPACE_AM @@ -79,7 +82,8 @@ static QString sysUiId = qSL(":sysui:"); // vvv IntentAMImplementation vvv -IntentServer *IntentAMImplementation::createIntentServerAndClientInstance(const QMap<QString, int> &timeouts) +IntentServer *IntentAMImplementation::createIntentServerAndClientInstance(PackageManager *packageManager, + const QMap<QString, int> &timeouts) { auto intentServerAMInterface = new IntentServerAMImplementation; auto intentClientAMInterface = new IntentClientAMImplementation(intentServerAMInterface); @@ -108,14 +112,55 @@ IntentServer *IntentAMImplementation::createIntentServerAndClientInstance(const if (it != timeouts.cend()) intentClient->setReplyFromSystemTimeout(it.value()); - - // 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 intentClient->setParent(intentServer); + + // connect the APIs of the PackageManager and the IntentServer + // the Intent API does not use AM internal types, so we have to translate using id strings: + // the idea behind this is that the Intent subsystem could be useful outside of the AM as well, + // so we try not to use AM specific classes in the intent-server and intent-client modules. + QObject::connect(packageManager, &PackageManager::packageAdded, + intentServer, &IntentServer::addPackage); + QObject::connect(packageManager, &PackageManager::packageAboutToBeRemoved, + intentServer, &IntentServer::removePackage); + + QObject::connect(&packageManager->internalSignals, &PackageManagerInternalSignals::registerApplication, + intentServer, [intentServer](ApplicationInfo *applicationInfo, Package *package) { + intentServer->addApplication(applicationInfo->id(), package->id()); + }); + QObject::connect(&packageManager->internalSignals, &PackageManagerInternalSignals::unregisterApplication, + intentServer, [intentServer](ApplicationInfo *applicationInfo, Package *package) { + intentServer->removeApplication(applicationInfo->id(), package->id()); + }); + + QObject::connect(&packageManager->internalSignals, &PackageManagerInternalSignals::registerIntent, + intentServer, [intentServer](IntentInfo *intentInfo, Package *package) { + + if (!intentServer->addIntent(intentInfo->id(), package->id(), intentInfo->handlingApplicationId(), + intentInfo->requiredCapabilities(), + intentInfo->visibility() == IntentInfo::Public ? Intent::Public + : Intent::Private, + intentInfo->parameterMatch(), intentInfo->names(), + QUrl::fromLocalFile(package->info()->baseDir().absoluteFilePath(intentInfo->icon())), + intentInfo->categories())) { + throw Exception(Error::Intents, "could not add intent %1 for package %2") + .arg(intentInfo->id()).arg(package->id()); + } + qCDebug(LogSystem).nospace().noquote() << " ++ intent: " << intentInfo->id() << " [package: " << package->id() << "]"; + }); + QObject::connect(&packageManager->internalSignals, &PackageManagerInternalSignals::unregisterIntent, + intentServer, [intentServer](IntentInfo *intentInfo, Package *package) { + Intent *intent = intentServer->packageIntent(intentInfo->id(), package->id(), + intentInfo->parameterMatch()); + qCDebug(LogSystem).nospace().noquote() << " -- intent: " << intentInfo->id() << " [package: " << package->id() << "]"; + Q_ASSERT(intent); + intentServer->removeIntent(intent); + }); return intentServer; } + // ^^^ IntentAMImplementation ^^^ ////////////////////////////////////////////////////////////////////////// // vvv IntentServerAMImplementation vvv diff --git a/src/manager-lib/intentaminterface.h b/src/manager-lib/intentaminterface.h index f026bbd4..6363516e 100644 --- a/src/manager-lib/intentaminterface.h +++ b/src/manager-lib/intentaminterface.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/ @@ -54,17 +55,18 @@ #include <QtAppManCommon/global.h> #include <QtAppManIntentServer/intentserversysteminterface.h> #include <QtAppManIntentClient/intentclientsysteminterface.h> +#include <QtAppManApplication/intentinfo.h> class IntentInterfaceAdaptor; QT_BEGIN_NAMESPACE_AM class Application; -class PackageInfo; +class PackageManager; class IntentServerRequest; namespace IntentAMImplementation { -IntentServer *createIntentServerAndClientInstance(const QMap<QString, int> &timeouts); +IntentServer *createIntentServerAndClientInstance(PackageManager *packageManager, const QMap<QString, int> &timeouts); } // the server side diff --git a/src/manager-lib/package.cpp b/src/manager-lib/package.cpp index d51ed794..7ad4b1cf 100644 --- a/src/manager-lib/package.cpp +++ b/src/manager-lib/package.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/ @@ -60,7 +61,12 @@ QString Package::id() const bool Package::isBuiltIn() const { - return info()->isBuiltIn(); + return m_info->isBuiltIn(); +} + +bool Package::builtInHasRemovableUpdate() const +{ + return isBuiltIn() && m_updatedInfo; } QString Package::version() const @@ -156,39 +162,40 @@ void Package::setProgress(qreal progress) m_progress = progress; } - -void Package::setBaseInfo(PackageInfo *info) -{ - m_info.reset(info); - emit bulkChange(); -} - -void Package::setUpdatedInfo(PackageInfo *info) +PackageInfo *Package::info() const { - Q_ASSERT(!info || (m_info && info->id() == m_info->id())); - - m_updatedInfo.reset(info); - emit bulkChange(); + return m_updatedInfo ? m_updatedInfo : m_info; } -PackageInfo *Package::info() const +PackageInfo *Package::baseInfo() const { - return m_updatedInfo ? m_updatedInfo.data() : m_info.data(); + return m_info; } PackageInfo *Package::updatedInfo() const { - return m_updatedInfo.data(); + return m_updatedInfo; } -PackageInfo *Package::takeBaseInfo() +PackageInfo *Package::setUpdatedInfo(PackageInfo *info) { - return m_info.take(); + Q_ASSERT(!info || (m_info && info->id() == m_info->id())); + Q_ASSERT(info != m_updatedInfo); + + auto old = m_updatedInfo; + m_updatedInfo = info; + emit bulkChange(); + return old; } -bool Package::canBeRevertedToBuiltIn() const +PackageInfo *Package::setBaseInfo(PackageInfo *info) { - return m_info && m_updatedInfo; + Q_ASSERT(info != m_info); + + auto old = m_info; + m_info = info; + emit bulkChange(); + return old; } bool Package::isBlocked() const @@ -200,8 +207,8 @@ bool Package::block() { bool blockedNow = (m_blocked.fetchAndAddOrdered(1) == 0); if (blockedNow) { - emit blockedChanged(true); m_blockedApps = info()->applications(); + emit blockedChanged(true); } return blockedNow; } diff --git a/src/manager-lib/package.h b/src/manager-lib/package.h index bf5fd4c2..690059f0 100644 --- a/src/manager-lib/package.h +++ b/src/manager-lib/package.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/ @@ -58,11 +59,12 @@ class Package : public QObject Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/PackageObject 2.0 UNCREATABLE") Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(bool builtIn READ isBuiltIn NOTIFY bulkChange) + Q_PROPERTY(bool builtInHasRemovableUpdate READ builtInHasRemovableUpdate NOTIFY bulkChange) Q_PROPERTY(QUrl icon READ icon NOTIFY bulkChange) Q_PROPERTY(QString version READ version NOTIFY bulkChange) Q_PROPERTY(QString name READ name NOTIFY bulkChange) Q_PROPERTY(QVariantMap names READ names NOTIFY bulkChange) - Q_PROPERTY(QString description READ version NOTIFY bulkChange) + Q_PROPERTY(QString description READ description NOTIFY bulkChange) Q_PROPERTY(QVariantMap descriptions READ descriptions NOTIFY bulkChange) Q_PROPERTY(QStringList categories READ categories NOTIFY bulkChange) Q_PROPERTY(State state READ state NOTIFY stateChanged) @@ -82,6 +84,7 @@ public: QString id() const; bool isBuiltIn() const; + bool builtInHasRemovableUpdate() const; QUrl icon() const; QString version() const; QString name() const; @@ -96,10 +99,6 @@ public: void setState(State state); void setProgress(qreal progress); - // Creates a list of Applications from a list of ApplicationInfo objects. - // Ownership of the given ApplicationInfo objects is passed to the returned Applications. - //static QVector<Application *> fromApplicationInfoVector(QVector<ApplicationInfo *> &); - /* All packages have a base info. @@ -114,15 +113,13 @@ public: back to a previous version. Regular packages get completely removed when requested. */ - void setBaseInfo(PackageInfo *info); - void setUpdatedInfo(PackageInfo *info); // Returns the updated info, if there's one. Otherwise returns the base info. PackageInfo *info() const; + PackageInfo *baseInfo() const; PackageInfo *updatedInfo() const; - PackageInfo *takeBaseInfo(); - - bool canBeRevertedToBuiltIn() const; + PackageInfo *setUpdatedInfo(PackageInfo *info); + PackageInfo *setBaseInfo(PackageInfo *info); bool isBlocked() const; bool block(); @@ -139,8 +136,8 @@ signals: void blockedChanged(bool blocked); private: - QScopedPointer<PackageInfo> m_info; - QScopedPointer<PackageInfo> m_updatedInfo; + PackageInfo *m_info = nullptr; + PackageInfo *m_updatedInfo = nullptr; State m_state = Installed; qreal m_progress = 0; diff --git a/src/manager-lib/packagemanager.cpp b/src/manager-lib/packagemanager.cpp index bd0d70f8..1a8dcaca 100644 --- a/src/manager-lib/packagemanager.cpp +++ b/src/manager-lib/packagemanager.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/ @@ -46,6 +47,8 @@ #include "packagemanager.h" #include "packagedatabase.h" #include "packagemanager_p.h" +#include "applicationinfo.h" +#include "intentinfo.h" #include "package.h" #include "logging.h" #include "installationreport.h" @@ -100,47 +103,147 @@ PackageManager *PackageManager::createInstance(PackageDatabase *packageDatabase, QScopedPointer<PackageManager> pm(new PackageManager(packageDatabase, documentPath)); registerQmlTypes(); + return s_instance = pm.take(); +} + +PackageManager *PackageManager::instance() +{ + if (!s_instance) + qFatal("PackageManager::instance() was called before createInstance()."); + return s_instance; +} + +QObject *PackageManager::instanceForQml(QQmlEngine *, QJSEngine *) +{ + QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership); + return instance(); +} + +void PackageManager::registerPackages() +{ + qCDebug(LogSystem) << "Registering packages:"; + + // collect all updates to builtin first, so we can avoid re-creating a lot of objects, + // if we find an update to a builin app later on + QMap<QString, QPair<PackageInfo *, PackageInfo *>> pkgs; + // map all the built-in packages first - const auto builtinPackages = packageDatabase->builtInPackages(); + const auto builtinPackages = d->database->builtInPackages(); for (auto packageInfo : builtinPackages) { - auto *package = new Package(packageInfo); - QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership); - pm->d->packages << package; + if (Package *existingPackage = fromId(packageInfo->id())) { + throw Exception(Error::Package, "Found more than one built-in package with id '%1': here: %2 and there: %3") + .arg(packageInfo->id()) + .arg(existingPackage->info()->manifestPath()) + .arg(packageInfo->manifestPath()); + } + pkgs.insert(packageInfo->id(), qMakePair(packageInfo, nullptr)); } // next, map all the installed packages, making sure to detect updates to built-in ones - const auto installedPackages = packageDatabase->installedPackages(); + const auto installedPackages = d->database->installedPackages(); for (auto packageInfo : installedPackages) { - Package *builtInPackage = pm->fromId(packageInfo->id()); + auto existingPackageInfos = pkgs.value(packageInfo->id()); + if (existingPackageInfos.first) { + if (existingPackageInfos.first->isBuiltIn()) { // update + if (existingPackageInfos.second) { // but there already is an update applied!? + throw Exception(Error::Package, "Found more than one update for the built-in package with id '%1' here: %2 and there: %3") + .arg(packageInfo->id()) + .arg(existingPackageInfos.second->manifestPath()) + .arg(packageInfo->manifestPath()); + } + pkgs[packageInfo->id()] = qMakePair(existingPackageInfos.first, packageInfo); - if (builtInPackage) { // update - if (builtInPackage->updatedInfo()) { // but there already is an update applied!? - throw Exception(Error::Package, "Found more than one update for the built-in package '%1'") - .arg(builtInPackage->id()); - //TODO: can we get the paths to both info.yaml here? + } else { + throw Exception(Error::Package, "Found more than one installed package with the same id '%1' here: %2 and there: %3") + .arg(packageInfo->id()) + .arg(existingPackageInfos.first->manifestPath()) + .arg(packageInfo->manifestPath()); } - builtInPackage->setUpdatedInfo(packageInfo); } else { - auto *package = new Package(packageInfo); - QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership); - pm->d->packages << package; + pkgs.insert(packageInfo->id(), qMakePair(packageInfo, nullptr)); } } + for (auto it = pkgs.constBegin(); it != pkgs.constEnd(); ++it) + registerPackage(it.value().first, it.value().second); +} + +void PackageManager::registerPackage(PackageInfo *packageInfo, PackageInfo *updatedPackageInfo, + bool currentlyBeingInstalled) +{ + auto *package = new Package(packageInfo, currentlyBeingInstalled ? Package::BeingInstalled + : Package::Installed); + if (updatedPackageInfo) + package->setUpdatedInfo(updatedPackageInfo); + + QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership); + + if (currentlyBeingInstalled) { + Q_ASSERT(package->block()); + + beginInsertRows(QModelIndex(), d->packages.count(), d->packages.count()); + qCDebug(LogSystem) << "Installing package:"; + } - return s_instance = pm.take(); + d->packages << package; + + qCDebug(LogSystem).nospace().noquote() << " + package: " << package->id() << " [at: " + << QDir().relativeFilePath(package->info()->baseDir().path()) << "]"; + + if (currentlyBeingInstalled) { + endInsertRows(); + emitDataChanged(package); + } + + emit packageAdded(package->id()); + + if (!currentlyBeingInstalled) + registerApplicationsAndIntentsOfPackage(package); } -PackageManager *PackageManager::instance() +void PackageManager::registerApplicationsAndIntentsOfPackage(Package *package) { - if (!s_instance) - qFatal("PackageManager::instance() was called before createInstance()."); - return s_instance; + const auto appInfos = package->info()->applications(); + for (auto appInfo : appInfos) { + try { + emit internalSignals.registerApplication(appInfo, package); + } catch (const Exception &e) { + qCWarning(LogSystem) << "Cannot register application" << appInfo->id() << ":" + << e.errorString(); + } + } + + const auto intentInfos = package->info()->intents(); + for (auto intentInfo : intentInfos) { + try { + emit internalSignals.registerIntent(intentInfo, package); + } catch (const Exception &e) { + qCWarning(LogSystem) << "Cannot register intent" << intentInfo->id() << ":" + << e.errorString(); + } + } } -QObject *PackageManager::instanceForQml(QQmlEngine *, QJSEngine *) +void PackageManager::unregisterApplicationsAndIntentsOfPackage(Package *package) { - QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership); - return instance(); + const auto intentInfos = package->info()->intents(); + for (auto intentInfo : intentInfos) { + try { + emit internalSignals.unregisterIntent(intentInfo, package); // not throwing ATM + } catch (const Exception &e) { + qCWarning(LogSystem) << "Cannot unregister intent" << intentInfo->id() << ":" + << e.errorString(); + } + } + + const auto appInfos = package->info()->applications(); + for (auto appInfo : appInfos) { + try { + emit internalSignals.unregisterApplication(appInfo, package); // not throwing ATM + } catch (const Exception &e) { + qCWarning(LogSystem) << "Cannot unregister application" << appInfo->id() << ":" + << e.errorString(); + } + } } QVector<Package *> PackageManager::packages() const @@ -180,6 +283,7 @@ PackageManager::PackageManager(PackageDatabase *packageDatabase, PackageManager::~PackageManager() { + qDeleteAll(d->packages); delete d->database; delete d; s_instance = nullptr; @@ -517,14 +621,13 @@ void PackageManager::cleanupBrokenInstallations() Q_DECL_NOEXCEPT_EXPR(false) if (ir) { bool valid = true; - QString pkgDir = d->installationPath + pkg->id(); + QString pkgDir = d->installationPath + QDir::separator() + pkg->id(); QStringList checkDirs; QStringList checkFiles; checkFiles << pkgDir + qSL("/info.yaml"); checkFiles << pkgDir + qSL("/.installation-report.yaml"); checkDirs << pkgDir; - checkDirs << d->installationPath + pkg->id(); for (const QString &checkFile : qAsConst(checkFiles)) { QFileInfo fi(checkFile); @@ -544,8 +647,8 @@ void PackageManager::cleanupBrokenInstallations() Q_DECL_NOEXCEPT_EXPR(false) } if (valid) { - validPaths.insertMulti(d->installationPath, pkg->id() + qL1C('/')); - validPaths.insertMulti(d->documentPath, pkg->id() + qL1C('/')); + validPaths.insertMulti(d->installationPath, pkg->id() + QDir::separator()); + validPaths.insertMulti(d->documentPath, pkg->id() + QDir::separator()); } else { if (startingPackageRemoval(pkg->id())) { if (finishedPackageInstall(pkg->id())) @@ -572,7 +675,7 @@ void PackageManager::cleanupBrokenInstallations() Q_DECL_NOEXCEPT_EXPR(false) for (const QFileInfo &fi : dirEntries) { QString name = fi.fileName(); if (fi.isDir()) - name.append(qL1C('/')); + name.append(QDir::separator()); if ((!fi.isDir() && !fi.isFile()) || !validNames.contains(name)) { qCDebug(LogInstaller) << "cleanup: removing unreferenced inode" << name; @@ -1030,42 +1133,22 @@ bool PackageManager::startingPackageInstallation(PackageInfo *info) if (!newInfo || newInfo->id().isEmpty()) return false; + Package *package = fromId(newInfo->id()); -// if (!RuntimeFactory::instance()->manager(newInfo->runtimeName())) -// return false; if (package) { // update if (!package->block()) return false; - if (package->isBuiltIn()) { - // overlay the existing base info - // we will rollback to the base one if this update is removed. - package->setUpdatedInfo(newInfo.take()); - } else { - // overwrite the existing base info - // we're not keeping track of the original. so removing the updated base version removes the - // application entirely. - package->setBaseInfo(newInfo.take()); - } + // do not overwrite the base-info / update-info yet - only after a successful installation + d->pendingPackageInfoUpdates.insert(package, newInfo.take()); + package->setState(Package::BeingUpdated); package->setProgress(0); emitDataChanged(package); } else { // installation - package = new Package(newInfo.take(), Package::BeingInstalled); - - Q_ASSERT(package->block()); - - beginInsertRows(QModelIndex(), d->packages.count(), d->packages.count()); - - QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership); - d->packages << package; - - endInsertRows(); - - emitDataChanged(package); - - emit packageAdded(package->id()); + // add a new package to the model and block it + registerPackage(newInfo.take(), nullptr, true); } return true; } @@ -1079,14 +1162,14 @@ bool PackageManager::startingPackageRemoval(const QString &id) if (package->isBlocked() || (package->state() != Package::Installed)) return false; - if (package->isBuiltIn() && !package->canBeRevertedToBuiltIn()) + if (package->isBuiltIn() && !package->builtInHasRemovableUpdate()) return false; if (!package->block()) // this will implicitly stop all apps in this package (asynchronously) return false; - package->setState(package->canBeRevertedToBuiltIn() ? Package::BeingDowngraded - : Package::BeingRemoved); + package->setState(package->builtInHasRemovableUpdate() ? Package::BeingDowngraded + : Package::BeingRemoved); package->setProgress(0); emitDataChanged(package, QVector<int> { IsUpdating }); @@ -1103,33 +1186,73 @@ bool PackageManager::finishedPackageInstall(const QString &id) case Package::Installed: return false; + case Package::BeingUpdated: case Package::BeingInstalled: - case Package::BeingUpdated: { - // The Package object has been updated right at the start of the installation/update. - // Now's the time to update the InstallationReport that was written by the installer. - QFile irfile(QDir(package->info()->baseDir()).absoluteFilePath(qSL(".installation-report.yaml"))); - QScopedPointer<InstallationReport> ir(new InstallationReport(package->id())); - if (!irfile.open(QFile::ReadOnly) || !ir->deserialize(&irfile)) { - qCCritical(LogInstaller) << "Could not read the new installation-report for package" - << package->id() << "at" << irfile.fileName(); - return false; + case Package::BeingDowngraded: { + bool isUpdate = (package->state() == Package::BeingUpdated); + bool isDowngrade = (package->state() == Package::BeingDowngraded); + + // figure out what the new info is + PackageInfo *newPackageInfo; + if (isUpdate) + newPackageInfo = d->pendingPackageInfoUpdates.take(package); + else if (isDowngrade) + newPackageInfo = nullptr; + else + newPackageInfo = package->baseInfo(); + + // attach the installation report (unless we're just downgrading a built-in) + if (!isDowngrade) { + QFile irfile(newPackageInfo->baseDir().absoluteFilePath(qSL(".installation-report.yaml"))); + QScopedPointer<InstallationReport> ir(new InstallationReport(package->id())); + irfile.open(QFile::ReadOnly); + try { + ir->deserialize(&irfile); + } catch (const Exception &e) { + qCCritical(LogInstaller) << "Could not read the new installation-report for package" + << package->id() << "at" << irfile.fileName() << ":" + << e.errorString(); + return false; + } + newPackageInfo->setInstallationReport(ir.take()); } - package->info()->setInstallationReport(ir.take()); + + if (isUpdate || isDowngrade) { + // unregister all the old apps & intents + unregisterApplicationsAndIntentsOfPackage(package); + + // update the correct base/updated info pointer + PackageInfo *oldPackageInfo; + if (package->isBuiltIn()) + oldPackageInfo = package->setUpdatedInfo(newPackageInfo); + else + oldPackageInfo = package->setBaseInfo(newPackageInfo); + + if (oldPackageInfo) + d->database->removePackageInfo(oldPackageInfo); + } + + // add the new info to the package db + if (newPackageInfo) + d->database->addPackageInfo(newPackageInfo); + + // register all the apps & intents + registerApplicationsAndIntentsOfPackage(package); + + // boiler-plate cleanup code package->setState(Package::Installed); package->setProgress(0); - emitDataChanged(package); - package->unblock(); emit package->bulkChange(); // not ideal, but icon and codeDir have changed break; } - case Package::BeingDowngraded: - package->setUpdatedInfo(nullptr); - package->setState(Package::Installed); - break; case Package::BeingRemoved: { + // unregister all the apps & intents + unregisterApplicationsAndIntentsOfPackage(package); + + // remove the package from the model int row = d->packages.indexOf(package); if (row >= 0) { emit packageAboutToBeRemoved(package->id()); @@ -1137,13 +1260,17 @@ bool PackageManager::finishedPackageInstall(const QString &id) d->packages.removeAt(row); endRemoveRows(); } + + // cleanup + package->unblock(); delete package; + + // remove the package from the package db + d->database->removePackageInfo(package->info()); break; } } - //emit internalSignals.applicationsChanged(); - return true; } @@ -1165,6 +1292,7 @@ bool PackageManager::canceledPackageInstall(const QString &id) d->packages.removeAt(row); endRemoveRows(); } + package->unblock(); delete package; break; } diff --git a/src/manager-lib/packagemanager.h b/src/manager-lib/packagemanager.h index fb1cb2ee..082fc5e3 100644 --- a/src/manager-lib/packagemanager.h +++ b/src/manager-lib/packagemanager.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/ @@ -60,6 +61,21 @@ class PackageDatabase; class Package; class PackageManagerPrivate; +// A place to collect signals used internally by appman without polluting +// PackageManager's public QML API. +class PackageManagerInternalSignals : public QObject +{ + Q_OBJECT +signals: + // the slots connected to these signals are allowed to throw Exception objects, if the + // connection is direct! + void registerApplication(ApplicationInfo *applicationInfo, Package *package); + void unregisterApplication(ApplicationInfo *applicationInfo, Package *package); + + void registerIntent(IntentInfo *intentInfo, Package *package); + void unregisterIntent(IntentInfo *intentInfo, Package *package); +}; + class PackageManager : public QAbstractListModel { Q_OBJECT @@ -79,6 +95,8 @@ class PackageManager : public QAbstractListModel Q_PROPERTY(uint commonApplicationGroupId READ commonApplicationGroupId) public: + Q_ENUMS(QT_PREPEND_NAMESPACE_AM(AsynchronousTask::TaskState)) + enum CacheMode { NoCache, UseCache, @@ -91,6 +109,8 @@ public: static PackageManager *instance(); static QObject *instanceForQml(QQmlEngine *qmlEngine, QJSEngine *); + void registerPackages(); + QVector<Package *> packages() const; Package *fromId(const QString &id) const; @@ -150,6 +170,7 @@ public: Q_SCRIPTABLE int compareVersions(const QString &version1, const QString &version2); Q_SCRIPTABLE bool validateDnsName(const QString &name, int minimumParts = 1); + PackageManagerInternalSignals internalSignals; signals: Q_SCRIPTABLE void countChanged(); @@ -183,6 +204,10 @@ protected: private: void emitDataChanged(Package *package, const QVector<int> &roles = QVector<int>()); + void registerPackage(PackageInfo *packageInfo, PackageInfo *updatedPackageInfo, + bool currentlyBeingInstalled = false); + void registerApplicationsAndIntentsOfPackage(Package *package); + void unregisterApplicationsAndIntentsOfPackage(Package *package); static void registerQmlTypes(); void triggerExecuteNextTask(); diff --git a/src/manager-lib/packagemanager_p.h b/src/manager-lib/packagemanager_p.h index 4e7a158b..b841a3c1 100644 --- a/src/manager-lib/packagemanager_p.h +++ b/src/manager-lib/packagemanager_p.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/ @@ -63,6 +64,8 @@ public: PackageDatabase *database = nullptr; QVector<Package *> packages; + QMap<Package *, PackageInfo *> pendingPackageInfoUpdates; + bool developmentMode = false; bool allowInstallationOfUnsignedPackages = false; bool userIdSeparation = false; diff --git a/src/src.pro b/src/src.pro index b5e66223..17746233 100644 --- a/src/src.pro +++ b/src/src.pro @@ -61,7 +61,7 @@ tools_testrunner.subdir = tools/testrunner tools_testrunner.depends = main_lib tools_dumpqmltypes.subdir = tools/dumpqmltypes -tools_dumpqmltypes.depends = manager_lib window_lib shared_main_lib main_lib launcher_lib +tools_dumpqmltypes.depends = manager_lib intent_server_lib window_lib shared_main_lib launcher_lib main_lib tools_packager.subdir = tools/packager tools_packager.depends = package_lib application_lib crypto_lib diff --git a/src/tools/launcher-qml/launcher-qml.cpp b/src/tools/launcher-qml/launcher-qml.cpp index c3eb49db..bbcf6d58 100644 --- a/src/tools/launcher-qml/launcher-qml.cpp +++ b/src/tools/launcher-qml/launcher-qml.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/ @@ -84,6 +85,7 @@ #include "exception.h" #include "crashhandler.h" #include "yamlpackagescanner.h" +#include "packageinfo.h" #include "applicationinfo.h" #include "startupinterface.h" #include "dbus-utilities.h" @@ -134,13 +136,13 @@ int main(int argc, char *argv[]) clp.addHelpOption(); clp.addOption({ qSL("qml-debug"), qSL("Enables QML debugging and profiling.") }); clp.addOption({ qSL("quicklaunch"), qSL("Starts the launcher in the quicklaunching mode.") }); - clp.addOption({ qSL("directload") , qSL("The info.yaml to start."), qSL("info.yaml") }); + clp.addOption({ qSL("directload") , qSL("The info.yaml to start (you can add '@<appid>' to start a specific app within the package, instead of the first one)."), qSL("info.yaml") }); clp.process(a); bool quicklaunched = clp.isSet(qSL("quicklaunch")); - QString directLoad = clp.value(qSL("directload")); + QString directLoadManifest = clp.value(qSL("directload")); - if (directLoad.isEmpty()) + if (directLoadManifest.isEmpty()) a.loadConfiguration(); CrashHandler::setCrashActionConfiguration(a.runtimeConfiguration().value(qSL("crashAction")).toMap()); @@ -152,17 +154,25 @@ int main(int argc, char *argv[]) StartupTimer::instance()->checkpoint("after basic initialization"); - if (!directLoad.isEmpty()) { - QFileInfo fi(directLoad); + if (!directLoadManifest.isEmpty()) { + QString directLoadAppId; + int appPos = directLoadManifest.indexOf(qSL("@")); + if (appPos > 0) { + directLoadAppId = directLoadManifest.mid(appPos + 1); + directLoadManifest.truncate(appPos); + } + + QFileInfo fi(directLoadManifest); if (!fi.exists() || fi.fileName() != qSL("info.yaml")) throw Exception("--directload needs a valid info.yaml file as parameter"); - directLoad = fi.absoluteFilePath(); + directLoadManifest = fi.absoluteFilePath(); + new Controller(&a, quicklaunched, qMakePair(directLoadManifest, directLoadAppId)); } else { a.setupDBusConnections(); StartupTimer::instance()->checkpoint("after dbus initialization"); + new Controller(&a, quicklaunched); } - new Controller(&a, quicklaunched, directLoad); return a.exec(); } catch (const std::exception &e) { @@ -171,8 +181,11 @@ int main(int argc, char *argv[]) } } +Controller::Controller(LauncherMain *a, bool quickLaunched) + : Controller(a, quickLaunched, qMakePair(QString{}, QString{})) +{ } -Controller::Controller(LauncherMain *a, bool quickLaunched, const QString &directLoad) +Controller::Controller(LauncherMain *a, bool quickLaunched, const QPair<QString, QString> &directLoad) : QObject(a) , m_quickLaunched(quickLaunched) { @@ -271,7 +284,7 @@ Controller::Controller(LauncherMain *a, bool quickLaunched, const QString &direc } } - if (directLoad.isEmpty()) { + if (directLoad.first.isEmpty()) { m_applicationInterface = new QmlApplicationInterface(a->p2pDBusName(), a->notificationDBusName(), this); connect(m_applicationInterface, &QmlApplicationInterface::startApplication, this, &Controller::startApplication); @@ -280,15 +293,32 @@ Controller::Controller(LauncherMain *a, bool quickLaunched, const QString &direc } else { QMetaObject::invokeMethod(this, [this, directLoad]() { - QFileInfo fi(directLoad); - YamlPackageScanner yps; + PackageInfo *pi; try { - //TODO: how should this work? -// ApplicationInfo *a = yps.scan(directLoad); -// startApplication(fi.absolutePath(), a->codeFilePath(), QString(), QString(), a->toVariantMap(), QVariantMap()); + pi = YamlPackageScanner().scan(directLoad.first); } catch (const Exception &e) { - throw Exception("Could not parse info.yaml file: %1").arg(e.what()); + qCCritical(LogQmlRuntime) << "Could not parse info.yaml file:" << e.what(); + QCoreApplication::exit(20); + return; } + const auto apps = pi->applications(); + const ApplicationInfo *a = apps.constFirst(); + if (!directLoad.second.isEmpty()) { + auto it = std::find_if(apps.cbegin(), apps.cend(), + [appId = directLoad.second](ApplicationInfo *appInfo) -> bool { + return (appInfo->id() == appId); + }); + if (it == apps.end()) { + qCCritical(LogQmlRuntime) << "Could not find the requested application id" + << directLoad.second << "within the info.yaml file"; + QCoreApplication::exit(21); + return; + } + a = *it; + } + + startApplication(QFileInfo(directLoad.first).absolutePath(), a->codeFilePath(), + QString(), QString(), a->toVariantMap(), QVariantMap()); }, Qt::QueuedConnection); } diff --git a/src/tools/launcher-qml/launcher-qml_p.h b/src/tools/launcher-qml/launcher-qml_p.h index 2ae2e8cf..abacfa57 100644 --- a/src/tools/launcher-qml/launcher-qml_p.h +++ b/src/tools/launcher-qml/launcher-qml_p.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/ @@ -74,7 +75,8 @@ class Controller : public QObject Q_OBJECT public: - Controller(LauncherMain *a, bool quickLaunched, const QString &directLoad = QString()); + Controller(LauncherMain *a, bool quickLaunched); + Controller(LauncherMain *a, bool quickLaunched, const QPair<QString, QString> &directLoad); public slots: void startApplication(const QString &baseDir, const QString &qmlFile, const QString &document, diff --git a/src/tools/packager/packagingjob.cpp b/src/tools/packager/packagingjob.cpp index f8868788..53c35a7f 100644 --- a/src/tools/packager/packagingjob.cpp +++ b/src/tools/packager/packagingjob.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/ @@ -46,7 +47,6 @@ #include "applicationinfo.h" #include "intentinfo.h" #include "installationreport.h" -#include "yamlpackagescanner.h" #include "packageextractor.h" #include "packagecreator.h" @@ -151,9 +151,8 @@ void PackagingJob::execute() Q_DECL_NOEXCEPT_EXPR(false) throw Exception(Error::Package, "source %1 is not a directory").arg(m_sourceDir); // check metadata - YamlPackageScanner yps; - QString infoName = yps.metaDataFileName(); - QScopedPointer<PackageInfo> package(yps.scan(source.absoluteFilePath(infoName))); + QString infoName = qSL("info.yaml"); + QScopedPointer<PackageInfo> package(PackageInfo::fromManifest(source.absoluteFilePath(infoName))); // build report InstallationReport report(package->id()); |