diff --git a/Modules/about-distro/autotests/OSReleaseTest.cpp b/Modules/about-distro/autotests/OSReleaseTest.cpp --- a/Modules/about-distro/autotests/OSReleaseTest.cpp +++ b/Modules/about-distro/autotests/OSReleaseTest.cpp @@ -31,24 +31,24 @@ void testParse() { auto r = OSRelease(QFINDTESTDATA("data/os-release")); - QCOMPARE(r.name, "Name"); - QCOMPARE(r.version, "100.5"); - QCOMPARE(r.id, "theid"); - QCOMPARE(r.idLike, QStringList({"otherid", "otherotherid"})); - QCOMPARE(r.versionCodename, "versioncodename"); - QCOMPARE(r.versionId, "500.1"); - QCOMPARE(r.prettyName, "Pretty Name"); - QCOMPARE(r.ansiColor, "1;34"); - QCOMPARE(r.cpeName, "cpe:/o:foo:bar:100"); - QCOMPARE(r.homeUrl, "https://url.home"); - QCOMPARE(r.documentationUrl, "https://url.docs"); - QCOMPARE(r.supportUrl, "https://url.support"); - QCOMPARE(r.bugReportUrl, "https://url.bugs"); - QCOMPARE(r.privacyPolicyUrl, "https://url.privacy"); - QCOMPARE(r.buildId, "105.5"); - QCOMPARE(r.variant, "Test Edition"); - QCOMPARE(r.variantId, "test"); - QCOMPARE(r.logo, "start-here-test"); + QCOMPARE(r.name(), "Name"); + QCOMPARE(r.version(), "100.5"); + QCOMPARE(r.id(), "theid"); + QCOMPARE(r.idLike(), QStringList({"otherid", "otherotherid"})); + QCOMPARE(r.versionCodename(), "versioncodename"); + QCOMPARE(r.versionId(), "500.1"); + QCOMPARE(r.prettyName(), "Pretty Name"); + QCOMPARE(r.ansiColor(), "1;34"); + QCOMPARE(r.cpeName(), "cpe:/o:foo:bar:100"); + QCOMPARE(r.homeUrl(), "https://url.home"); + QCOMPARE(r.documentationUrl(), "https://url.docs"); + QCOMPARE(r.supportUrl(), "https://url.support"); + QCOMPARE(r.bugReportUrl(), "https://url.bugs"); + QCOMPARE(r.privacyPolicyUrl(), "https://url.privacy"); + QCOMPARE(r.buildId(), "105.5"); + QCOMPARE(r.variant(), "Test Edition"); + QCOMPARE(r.variantId(), "test"); + QCOMPARE(r.logo(), "start-here-test"); } }; diff --git a/Modules/about-distro/src/Module.cpp b/Modules/about-distro/src/Module.cpp --- a/Modules/about-distro/src/Module.cpp +++ b/Modules/about-distro/src/Module.cpp @@ -150,7 +150,7 @@ OSRelease os; - QString logoPath = cg.readEntry("LogoPath", os.logo); + QString logoPath = cg.readEntry("LogoPath", os.logo()); if (logoPath.isEmpty()) { logoPath = QStringLiteral("start-here-kde"); } @@ -160,10 +160,10 @@ // We allow overriding of the OS name for branding purposes. // For example OS Ubuntu may be rebranded as Kubuntu. Also Kubuntu Active // as a product brand is different from Kubuntu. - const QString distroName = cg.readEntry("Name", os.name); + const QString distroName = cg.readEntry("Name", os.name()); const QString osrVersion = cg.readEntry("UseOSReleaseVersion", false) - ? os.version - : os.versionId; + ? os.version() + : os.versionId(); const QString versionId = cg.readEntry("Version", osrVersion); const QString distroNameVersion = QStringLiteral("%1 %2").arg(distroName, versionId); ui->nameVersionLabel->setText(distroNameVersion); @@ -173,14 +173,14 @@ labelsForClipboard << qMakePair(dummyDistroDescriptionLabel, ui->nameVersionLabel); englishTextForClipboard += QStringLiteral("Operating System: %1\n").arg(distroNameVersion); - const QString variant = cg.readEntry("Variant", os.variant); + const QString variant = cg.readEntry("Variant", os.variant()); if (variant.isEmpty()) { ui->variantLabel->hide(); } else { ui->variantLabel->setText(variant); } - const QString url = cg.readEntry("Website", os.homeUrl); + const QString url = cg.readEntry("Website", os.homeUrl()); if (url.isEmpty()) { ui->urlLabel->hide(); } else { diff --git a/Modules/about-distro/src/OSRelease.h b/Modules/about-distro/src/OSRelease.h --- a/Modules/about-distro/src/OSRelease.h +++ b/Modules/about-distro/src/OSRelease.h @@ -24,33 +24,73 @@ #include #include +/** + * @brief The OSRelease class parses /etc/os-release files + * + * https://www.freedesktop.org/software/systemd/man/os-release.html + * + * os-release is a free software standard for describing an operating system. + * This class parses and models os-release files. + */ class OSRelease { public: - OSRelease(const QString &filePath = defaultFilePath()); + /** + * Constructs a new OSRelease instance. Parsing happens in the constructor + * and the data is not cached across instances. + * + * @note For parsing simplicity neither trailing comments nor multiple '=' + * characters are allowed. + * + * @param filePath The path to the os-release file. By default the first + * available file of the paths specified in the os-release manpage is + * parsed. + */ + explicit OSRelease(const QString &filePath = defaultFilePath()); + ~OSRelease(); + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#NAME= */ + QString name() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION= */ + QString version() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ID= */ + QString id() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ID_LIKE= */ + QStringList idLike() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION_CODENAME= */ + QString versionCodename() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION_ID= */ + QString versionId() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#PRETTY_NAME= */ + QString prettyName() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ANSI_COLOR= */ + QString ansiColor() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#CPE_NAME= */ + QString cpeName() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ + QString homeUrl() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ + QString documentationUrl() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ + QString supportUrl() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ + QString bugReportUrl() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ + QString privacyPolicyUrl() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#BUILD_ID= */ + QString buildId() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VARIANT= */ + QString variant() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VARIANT_ID= */ + QString variantId() const; + /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#LOGO= */ + QString logo() const; + +private: static QString defaultFilePath(); - QString name; - QString version; - QString id; - QStringList idLike; - QString versionCodename; - QString versionId; - QString prettyName; - QString ansiColor; - QString cpeName; - // TODO: url struct or map? - QString homeUrl; - QString documentationUrl; - QString supportUrl; - QString bugReportUrl; - QString privacyPolicyUrl; - - QString buildId; - QString variant; - QString variantId; - QString logo; + class Private; + Private *const d = nullptr; }; #endif // OSRELEASE_H diff --git a/Modules/about-distro/src/OSRelease.cpp b/Modules/about-distro/src/OSRelease.cpp --- a/Modules/about-distro/src/OSRelease.cpp +++ b/Modules/about-distro/src/OSRelease.cpp @@ -25,7 +25,8 @@ #include -static void setVar(QString *var, const QString &value) +// Sets a QString var +static void setVar(QString *var, const QString &value) { // Values may contain quotation marks, strip them as we have no use for them. KShell::Errors error; @@ -36,6 +37,7 @@ *var = args.join(QLatin1Char(' ')); } +// Sets a QStringList var (i.e. splits a string value) static void setVar(QStringList *var, const QString &value) { // Instead of passing the verbatim value we manually strip any initial quotes @@ -60,77 +62,207 @@ *var = args; } -OSRelease::OSRelease(const QString &filePath) +class Q_DECL_HIDDEN OSRelease::Private { - // Set default values for non-optional fields. - name = QStringLiteral("Linux"); - id = QStringLiteral("linux"); - prettyName = QStringLiteral("Linux"); +public: + Private(const QString &filePath) + : name(QStringLiteral("Linux")) + , id(QStringLiteral("linux")) + , prettyName(QStringLiteral("Linux")) + { + // Default values for non-optional fields set above ^. - if (filePath.isEmpty()) { - return; - } - - QHash stringHash = { - { QStringLiteral("NAME"), &name }, - { QStringLiteral("VERSION"), &version }, - { QStringLiteral("ID"), &id }, - // idLike is not a QString, special handling below! - { QStringLiteral("VERSION_CODENAME"), &versionCodename }, - { QStringLiteral("VERSION_ID"), &versionId }, - { QStringLiteral("PRETTY_NAME"), &prettyName }, - { QStringLiteral("ANSI_COLOR"), &ansiColor }, - { QStringLiteral("CPE_NAME"), &cpeName }, - { QStringLiteral("HOME_URL"), &homeUrl }, - { QStringLiteral("DOCUMENTATION_URL"), &documentationUrl }, - { QStringLiteral("SUPPORT_URL"), &supportUrl }, - { QStringLiteral("BUG_REPORT_URL"), &bugReportUrl }, - { QStringLiteral("PRIVACY_POLICY_URL"), &privacyPolicyUrl }, - { QStringLiteral("BUILD_ID"), &buildId }, - { QStringLiteral("VARIANT"), &variant }, - { QStringLiteral("VARIANT_ID"), &variantId }, - { QStringLiteral("LOGO"), &logo } - }; - - QFile file(filePath); - // NOTE: The os-release specification defines default values for specific - // fields which means that even if we can not read the os-release file - // we have sort of expected default values to use. - // TODO: it might still be handy to indicate to the outside whether - // fallback values are being used or not. - file.open(QIODevice::ReadOnly | QIODevice::Text); - QString line; - QStringList comps; - while (!file.atEnd()) { - line = QString::fromLatin1(file.readLine()); - - if (line.startsWith(QLatin1Char('#'))) { - // Comment line - continue; + if (filePath.isEmpty()) { + return; } - comps = line.split(QLatin1Char('=')); + QHash stringHash = { + { QStringLiteral("NAME"), &name }, + { QStringLiteral("VERSION"), &version }, + { QStringLiteral("ID"), &id }, + // idLike is not a QString, special handling below! + { QStringLiteral("VERSION_CODENAME"), &versionCodename }, + { QStringLiteral("VERSION_ID"), &versionId }, + { QStringLiteral("PRETTY_NAME"), &prettyName }, + { QStringLiteral("ANSI_COLOR"), &ansiColor }, + { QStringLiteral("CPE_NAME"), &cpeName }, + { QStringLiteral("HOME_URL"), &homeUrl }, + { QStringLiteral("DOCUMENTATION_URL"), &documentationUrl }, + { QStringLiteral("SUPPORT_URL"), &supportUrl }, + { QStringLiteral("BUG_REPORT_URL"), &bugReportUrl }, + { QStringLiteral("PRIVACY_POLICY_URL"), &privacyPolicyUrl }, + { QStringLiteral("BUILD_ID"), &buildId }, + { QStringLiteral("VARIANT"), &variant }, + { QStringLiteral("VARIANT_ID"), &variantId }, + { QStringLiteral("LOGO"), &logo } + }; - if (comps.size() != 2) { - // Invalid line. - continue; - } + QFile file(filePath); + // NOTE: The os-release specification defines default values for specific + // fields which means that even if we can not read the os-release file + // we have sort of expected default values to use. + // TODO: it might still be handy to indicate to the outside whether + // fallback values are being used or not. + file.open(QIODevice::ReadOnly | QIODevice::Text); + QString line; + QStringList parts; + while (!file.atEnd()) { + // Trimmed to handle indented comment lines properly + line = QString::fromLatin1(file.readLine()).trimmed(); - QString key = comps.at(0); - QString value = comps.at(1).trimmed(); + if (line.startsWith(QLatin1Char('#'))) { + // Comment line + continue; + } - if (QString *var = stringHash.value(key, nullptr)) { - setVar(var, value); - } + parts = line.split(QLatin1Char('=')); - // ID_LIKE is a list and parsed as such (rather than a QString). - if (key == QLatin1String("ID_LIKE")) { - setVar(&idLike, value); - } + if (parts.size() != 2 || line.contains(QChar('#'))) { + // Invalid line... + // For the purposes of simple parsing we'll not support >2 = + // or >1 # characters. + // The former makes splitting and the latter makes comment + // stripping difficult. + continue; + } + + QString key = parts.at(0); + QString value = parts.at(1).trimmed(); + + if (QString *var = stringHash.value(key, nullptr)) { + setVar(var, value); + continue; + } + + // ID_LIKE is a list and parsed as such (rather than a QString). + if (key == QLatin1String("ID_LIKE")) { + setVar(&idLike, value); + continue; + } // os-release explicitly allows for vendor specific aditions. We have no // interest in those right now. + } } + + QString name; + QString version; + QString id; + QStringList idLike; + QString versionCodename; + QString versionId; + QString prettyName; + QString ansiColor; + QString cpeName; + QString homeUrl; + QString documentationUrl; + QString supportUrl; + QString bugReportUrl; + QString privacyPolicyUrl; + QString buildId; + QString variant; + QString variantId; + QString logo; +}; + +OSRelease::OSRelease(const QString &filePath) + : d(new Private(filePath)) +{ +} + +OSRelease::~OSRelease() +{ + delete d; +} + +QString OSRelease::name() const +{ + return d->name; +} + +QString OSRelease::version() const +{ + return d->version; +} + +QString OSRelease::id() const +{ + return d->id; +} + +QStringList OSRelease::idLike() const +{ + return d->idLike; +} + +QString OSRelease::versionCodename() const +{ + return d->versionCodename; +} + +QString OSRelease::versionId() const +{ + return d->versionId; +} + +QString OSRelease::prettyName() const +{ + return d->prettyName; +} + +QString OSRelease::ansiColor() const +{ + return d->ansiColor; +} + +QString OSRelease::cpeName() const +{ + return d->cpeName; +} + +QString OSRelease::homeUrl() const +{ + return d->homeUrl; +} + +QString OSRelease::documentationUrl() const +{ + return d->documentationUrl; +} + +QString OSRelease::supportUrl() const +{ + return d->supportUrl; +} + +QString OSRelease::bugReportUrl() const +{ + return d->bugReportUrl; +} + +QString OSRelease::privacyPolicyUrl() const +{ + return d->privacyPolicyUrl; +} + +QString OSRelease::buildId() const +{ + return d->buildId; +} + +QString OSRelease::variant() const +{ + return d->variant; +} + +QString OSRelease::variantId() const +{ + return d->variantId; +} + +QString OSRelease::logo() const +{ + return d->logo; } QString OSRelease::defaultFilePath()