From 1f73eea404709634b7c7d1f841acf797c46408cb Mon Sep 17 00:00:00 2001 From: ComixHe Date: Mon, 16 Oct 2023 14:39:20 +0800 Subject: [PATCH] feat: add desktopfilegenerator and method addUserApplication 1. change type of ActionName to 'a{sa{ss}}' 2. refactor the method of serialization Signed-off-by: ComixHe --- ...opspec.ApplicationManager1.Application.xml | 12 +- .../org.desktopspec.ApplicationManager1.xml | 15 ++ src/constant.h | 3 +- src/dbus/applicationmanager1service.cpp | 65 +++++++ src/dbus/applicationmanager1service.h | 3 +- src/dbus/applicationservice.cpp | 45 +++-- src/dbus/applicationservice.h | 12 +- src/desktopentry.cpp | 4 +- src/desktopentry.h | 5 +- src/desktopfilegenerator.cpp | 183 ++++++++++++++++++ src/desktopfilegenerator.h | 24 +++ src/desktopfileparser.cpp | 48 ++++- src/desktopfileparser.h | 2 + src/global.h | 17 +- tests/ut_desktopfilegenerator.cpp | 61 ++++++ 15 files changed, 458 insertions(+), 41 deletions(-) create mode 100644 src/desktopfilegenerator.cpp create mode 100644 src/desktopfilegenerator.h create mode 100644 tests/ut_desktopfilegenerator.cpp diff --git a/api/dbus/org.desktopspec.ApplicationManager1.Application.xml b/api/dbus/org.desktopspec.ApplicationManager1.Application.xml index cf18f2f..a73205b 100644 --- a/api/dbus/org.desktopspec.ApplicationManager1.Application.xml +++ b/api/dbus/org.desktopspec.ApplicationManager1.Application.xml @@ -44,7 +44,7 @@ @@ -56,10 +56,10 @@ /> - + @@ -69,7 +69,7 @@ name="org.freedesktop.DBus.Description" value="The type of IconName is a Map, where the key represents the action and the value is the corresponding content." /> - + @@ -77,7 +77,7 @@ name="org.freedesktop.DBus.Description" value="The meaning of this property's type is same as which in ActionName." /> - + @@ -85,7 +85,7 @@ name="org.freedesktop.DBus.Description" value="The meaning of this property's type is same as which in ActionName." /> - + diff --git a/api/dbus/org.desktopspec.ApplicationManager1.xml b/api/dbus/org.desktopspec.ApplicationManager1.xml index 2e31463..2dd570a 100644 --- a/api/dbus/org.desktopspec.ApplicationManager1.xml +++ b/api/dbus/org.desktopspec.ApplicationManager1.xml @@ -30,5 +30,20 @@ 1. You should use pidfd_open(2) to get a pidfd." /> + + + + + + + diff --git a/src/constant.h b/src/constant.h index f449603..4f3e707 100644 --- a/src/constant.h +++ b/src/constant.h @@ -21,6 +21,7 @@ constexpr auto DDEApplicationManager1JobManager1ObjectPath = u8"/org/desktopspec constexpr auto DDEApplicationManager1MimeManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/MimeManager1"; constexpr auto DesktopFileEntryKey = u8"Desktop Entry"; constexpr auto DesktopFileActionKey = u8"Desktop Action "; +constexpr auto DesktopFileDefaultKeyLocale = "default"; constexpr auto ApplicationManagerServerDBusName = #ifdef DDE_AM_USE_DEBUG_DBUS_NAME @@ -52,7 +53,7 @@ constexpr auto AppExecOption = u8"appExec"; constexpr auto STORAGE_VERSION = 0; constexpr auto ApplicationPropertiesGroup = u8"Application Properties"; constexpr auto LastLaunchedTime = u8"LastLaunchedTime"; -constexpr auto ScaleFactor=u8"ScaleFactor"; +constexpr auto ScaleFactor = u8"ScaleFactor"; constexpr auto ApplicationManagerHookDir = u8"/deepin/dde-application-manager/hooks.d"; diff --git a/src/dbus/applicationmanager1service.cpp b/src/dbus/applicationmanager1service.cpp index 32d2364..1608a6f 100644 --- a/src/dbus/applicationmanager1service.cpp +++ b/src/dbus/applicationmanager1service.cpp @@ -8,6 +8,7 @@ #include "systemdsignaldispatcher.h" #include "propertiesForwarder.h" #include "applicationHooks.h" +#include "desktopfilegenerator.h" #include #include #include @@ -548,3 +549,67 @@ ApplicationManager1Service::findApplicationsByIds(const QStringList &appIds) con return ret; } + +QString ApplicationManager1Service::addUserApplication(const QVariantMap &desktop_file, const QString &name) noexcept +{ + if (name.isEmpty()) { + sendErrorReply(QDBusError::Failed, "file name is empty."); + return {}; + } + + QDir xdgDataHome{getXDGDataHome() + "/applications"}; + const auto &filePath = xdgDataHome.filePath(name); + + if (QFileInfo info{filePath}; info.exists() and info.isFile()) { + sendErrorReply(QDBusError::Failed, QString{"file already exists:%1"}.arg(info.absoluteFilePath())); + return {}; + } + + QFile file{filePath}; + if (!file.open(QFile::NewOnly | QFile::WriteOnly | QFile::Text)) { + sendErrorReply(QDBusError::Failed, file.errorString()); + return {}; + } + + QString errMsg; + auto fileContent = DesktopFileGenerator::generate(desktop_file, errMsg); + if (fileContent.isEmpty() or !errMsg.isEmpty()) { + file.remove(); + sendErrorReply(QDBusError::Failed, errMsg); + return {}; + } + + auto writeContent = fileContent.toLocal8Bit(); + if (file.write(writeContent) != writeContent.size()) { + file.remove(); + sendErrorReply(QDBusError::Failed, "incomplete file content.this file will be removed."); + return {}; + } + + file.flush(); + + ParserError err{ParserError::NoError}; + auto ret = DesktopFile::searchDesktopFileByPath(filePath, err); + if (err != ParserError::NoError) { + file.remove(); + qDebug() << "add user's application failed:" << err; + sendErrorReply(QDBusError::Failed, "search failed."); + return {}; + } + + if (!ret) { + file.remove(); + sendErrorReply(QDBusError::InternalError); + return {}; + } + + auto desktopSource = std::move(ret).value(); + auto appId = desktopSource.desktopId(); + if (!addApplication(std::move(desktopSource))) { + file.remove(); + sendErrorReply(QDBusError::Failed, "add application to ApplicationManager failed."); + return {}; + } + + return appId; +} diff --git a/src/dbus/applicationmanager1service.h b/src/dbus/applicationmanager1service.h index 5445d49..7eea1e1 100644 --- a/src/dbus/applicationmanager1service.h +++ b/src/dbus/applicationmanager1service.h @@ -20,7 +20,7 @@ class ApplicationService; -class ApplicationManager1Service final : public QObject +class ApplicationManager1Service final : public QObject, public QDBusContext { Q_OBJECT public: @@ -54,6 +54,7 @@ public Q_SLOTS: QDBusObjectPath &instance, ObjectInterfaceMap &application_instance_info) const noexcept; void ReloadApplications(); + QString addUserApplication(const QVariantMap &desktop_file, const QString &name) noexcept; [[nodiscard]] ObjectMap GetManagedObjects() const; Q_SIGNALS: diff --git a/src/dbus/applicationservice.cpp b/src/dbus/applicationservice.cpp index 9b74c91..cf1dcab 100644 --- a/src/dbus/applicationservice.cpp +++ b/src/dbus/applicationservice.cpp @@ -384,59 +384,64 @@ QStringList ApplicationService::categories() const noexcept PropMap ApplicationService::actionName() const noexcept { PropMap ret; - auto actionList = actions(); + const auto &actionList = actions(); - for (auto &action : actionList) { - action.prepend(DesktopFileActionKey); - auto value = m_entry->value(action, "Name"); + for (const auto &action : actionList) { + auto rawActionKey = DesktopFileActionKey % action; + auto value = m_entry->value(rawActionKey, "Name"); if (!value.has_value()) { continue; } - ret.insert(action, std::move(value).value()); + ret.insert(action, std::move(value).value().value()); } return ret; } -PropMap ApplicationService::name() const noexcept +QStringMap ApplicationService::name() const noexcept { - PropMap ret; auto value = m_entry->value(DesktopFileEntryKey, "Name"); if (!value) { - return ret; + return {}; } - ret.insert(QString{"Name"}, {std::move(value).value()}); - return ret; + if (!value->canConvert()) { + return {}; + } + + return value->value(); } -PropMap ApplicationService::genericName() const noexcept +QStringMap ApplicationService::genericName() const noexcept { - PropMap ret; auto value = m_entry->value(DesktopFileEntryKey, "GenericName"); if (!value) { - return ret; + return {}; } - ret.insert(QString{"GenericName"}, {std::move(value).value()}); - return ret; + if (!value->canConvert()) { + return {}; + } + + return value->value(); } -PropMap ApplicationService::icons() const noexcept +QStringMap ApplicationService::icons() const noexcept { - PropMap ret; + QStringMap ret; auto actionList = actions(); for (const auto &action : actionList) { - auto value = m_entry->value(QString{action}.prepend(DesktopFileActionKey), "Icon"); + const auto &actionKey = QString{action}.prepend(DesktopFileActionKey); + auto value = m_entry->value(actionKey, "Icon"); if (!value.has_value()) { continue; } - ret.insert(action, {std::move(value).value()}); + ret.insert(actionKey, value->value()); } auto mainIcon = m_entry->value(DesktopFileEntryKey, "Icon"); if (mainIcon.has_value()) { - ret.insert(defaultKeyStr, {std::move(mainIcon).value()}); + ret.insert(DesktopFileEntryKey, mainIcon->value()); } return ret; diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index 2f8a190..f8e1f7a 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -50,14 +50,14 @@ public: Q_PROPERTY(QString ID READ id CONSTANT) [[nodiscard]] QString id() const noexcept; - Q_PROPERTY(PropMap Name READ name NOTIFY nameChanged) - [[nodiscard]] PropMap name() const noexcept; + Q_PROPERTY(QStringMap Name READ name NOTIFY nameChanged) + [[nodiscard]] QStringMap name() const noexcept; - Q_PROPERTY(PropMap GenericName READ genericName NOTIFY genericNameChanged) - [[nodiscard]] PropMap genericName() const noexcept; + Q_PROPERTY(QStringMap GenericName READ genericName NOTIFY genericNameChanged) + [[nodiscard]] QStringMap genericName() const noexcept; - Q_PROPERTY(PropMap Icons READ icons NOTIFY iconsChanged) - [[nodiscard]] PropMap icons() const noexcept; + Q_PROPERTY(QStringMap Icons READ icons NOTIFY iconsChanged) + [[nodiscard]] QStringMap icons() const noexcept; Q_PROPERTY(qulonglong LastLaunchedTime READ lastLaunchedTime NOTIFY lastLaunchedTimeChanged) [[nodiscard]] qulonglong lastLaunchedTime() const noexcept; diff --git a/src/desktopentry.cpp b/src/desktopentry.cpp index 96e6cf2..4c41c29 100644 --- a/src/desktopentry.cpp +++ b/src/desktopentry.cpp @@ -285,7 +285,7 @@ QString toString(const DesktopEntry::Value &value) noexcept QString str; if (value.canConvert()) { // get default locale - str = value.value()[defaultKeyStr]; + str = value.value()[DesktopFileDefaultKeyLocale]; } else { str = value.toString(); } @@ -312,7 +312,7 @@ QString toLocaleString(const QStringMap &localeMap, const QLocale &locale) noexc } } - return toString(localeMap[defaultKeyStr]); + return toString(localeMap[DesktopFileDefaultKeyLocale]); } QString toIconString(const DesktopEntry::Value &value) noexcept diff --git a/src/desktopentry.h b/src/desktopentry.h index 933c86f..74f5155 100644 --- a/src/desktopentry.h +++ b/src/desktopentry.h @@ -15,8 +15,6 @@ #include "iniParser.h" #include "global.h" -constexpr static auto defaultKeyStr = "default"; - enum class EntryContext { Unknown, EntryOuter, Entry, Done }; enum class EntryValueType { String, LocaleString, Boolean, IconString }; @@ -126,6 +124,9 @@ private: [[nodiscard]] bool checkMainEntryValidation() const noexcept; QMap> m_entryMap; bool m_parsed{false}; + +public: + using container_type = decltype(m_entryMap); }; bool operator==(const DesktopEntry &lhs, const DesktopEntry &rhs); diff --git a/src/desktopfilegenerator.cpp b/src/desktopfilegenerator.cpp new file mode 100644 index 0000000..d70022a --- /dev/null +++ b/src/desktopfilegenerator.cpp @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#include "desktopfilegenerator.h" +#include "desktopfileparser.h" + +bool DesktopFileGenerator::checkValidation(const QVariantMap &desktopFile, QString &err) noexcept +{ + if (!desktopFile.contains("Type") or !desktopFile.contains("Name")) { + err = "required key doesn't exists."; + return false; + } + + auto type = qdbus_cast(desktopFile["Type"]); + if (type.isEmpty()) { + err = "Type's type is invalid"; + return false; + } + + if (type == "Link" and !desktopFile.contains("URL")) { + err = "URL must be set when Type is 'Link'"; + return false; + } + return true; +} + +int DesktopFileGenerator::processMainGroupLocaleEntry(DesktopEntry::container_type::iterator mainEntry, + const QString &key, + const QVariant &value) noexcept +{ + if (key == "ActionName") { + return 1; + } + + if (key == "Name") { + const auto &nameMap = qdbus_cast(value); + if (nameMap.isEmpty()) { + qDebug() << "Name's type mismatch:" << nameMap; + return -1; + } + + mainEntry->insert("Name", QVariant::fromValue(nameMap)); + return 1; + } + + if (key == "Icon") { + const auto &iconMap = qdbus_cast(value); + if (auto icon = iconMap.constFind(DesktopFileDefaultKeyLocale); icon != iconMap.cend() and !icon->isEmpty()) { + mainEntry->insert("Icon", *icon); + } + return 1; + } + + if (key == "Exec") { + const auto &execMap = qdbus_cast(value); + if (auto exec = execMap.constFind(DesktopFileDefaultKeyLocale); exec != execMap.cend() and !exec->isEmpty()) { + mainEntry->insert("Exec", *exec); + } + return 1; + } + + return 0; +} + +bool DesktopFileGenerator::processMainGroup(DesktopEntry::container_type &content, const QVariantMap &rawValue) noexcept +{ + auto mainEntry = content.insert(DesktopFileEntryKey, {}); + for (auto it = rawValue.constKeyValueBegin(); it != rawValue.constKeyValueEnd(); ++it) { + const auto &[key, value] = *it; + + if (mainEntry->contains(key)) { + qDebug() << "duplicate key:" << key << ",skip"; + return false; + } + + auto ret = processMainGroupLocaleEntry(mainEntry, key, value); + if (ret == 1) { + continue; + } + + if (ret == -1) { + return false; + } + + mainEntry->insert(key, value); + } + + mainEntry->insert("X-Deepin-CreateBy", QString{"dde-application-manager"}); + return true; +} + +bool DesktopFileGenerator::processActionGroup(QStringList actions, + DesktopEntry::container_type &content, + const QVariantMap &rawValue) noexcept +{ + actions.removeDuplicates(); + if (actions.isEmpty()) { + qDebug() << "empty actions"; + return false; + } + + auto nameMap = qdbus_cast(rawValue["ActionName"]); + if (nameMap.isEmpty()) { + qDebug() << "ActionName's type mismatch."; + return false; + } + + QStringMap iconMap; + if (auto actionIcon = rawValue.constFind("Icon"); actionIcon != rawValue.cend()) { + iconMap = qdbus_cast(*actionIcon); + if (iconMap.isEmpty()) { + qDebug() << "Icon's type mismatch."; + return false; + } + } + + QStringMap execMap; + if (auto actionExec = rawValue.constFind("Exec"); actionExec != rawValue.cend()) { + execMap = qdbus_cast(*actionExec); + if (execMap.isEmpty()) { + qDebug() << "Exec's type mismatch:" << actionExec->typeName(); + return false; + } + } + + for (const auto &action : actions) { + if (action.isEmpty()) { + qDebug() << "action's content is empty. skip"; + continue; + } + + if (!nameMap.contains(action)) { + qDebug() << "couldn't find actionName, current action:" << action; + return false; + } + + auto actionGroup = content.insert(DesktopFileActionKey % action, {}); + auto curVal = qdbus_cast(nameMap[action]); + if (curVal.isEmpty()) { + qDebug() << "inner type of actionName is mismatched"; + return false; + } + actionGroup->insert("Name", QVariant::fromValue(curVal)); + + if (auto actionIcon = iconMap.constFind(action); actionIcon != iconMap.cend() and !actionIcon->isEmpty()) { + actionGroup->insert("Icon", iconMap[action]); + } + if (auto actionExec = execMap.constFind(action); actionExec != execMap.cend() and !actionExec->isEmpty()) { + actionGroup->insert("Exec", execMap[action]); + } + }; + + return true; +} + +QString DesktopFileGenerator::generate(const QVariantMap &desktopFile, QString &err) noexcept +{ + DesktopEntry::container_type content{}; + if (auto actions = desktopFile.find("Actions"); actions != desktopFile.end()) { + if (!desktopFile.contains("ActionName")) { + err = "'ActionName' doesn't exists"; + return {}; + } + + if (!processActionGroup(actions->toStringList(), content, desktopFile)) { + err = "please check action group"; + return {}; + } + } + + if (!processMainGroup(content, desktopFile)) { + err = "please check main group."; + return {}; + } + + auto fileContent = toString(content); + if (fileContent.isEmpty()) { + err = "couldn't convert to desktop file."; + return {}; + } + + return fileContent; +} diff --git a/src/desktopfilegenerator.h b/src/desktopfilegenerator.h new file mode 100644 index 0000000..2693b15 --- /dev/null +++ b/src/desktopfilegenerator.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DESKTOPFILEGENERATOR_H +#define DESKTOPFILEGENERATOR_H + +#include "desktopentry.h" + +struct DesktopFileGenerator +{ + static QString generate(const QVariantMap &desktopFile, QString &err) noexcept; + +private: + static bool checkValidation(const QVariantMap &desktopFile, QString &err) noexcept; + static bool processMainGroup(DesktopEntry::container_type &content, const QVariantMap &rawValue) noexcept; + static bool + processActionGroup(QStringList actions, DesktopEntry::container_type &content, const QVariantMap &rawValue) noexcept; + static int processMainGroupLocaleEntry(DesktopEntry::container_type::iterator mainEntry, + const QString &key, + const QVariant &value) noexcept; +}; + +#endif diff --git a/src/desktopfileparser.cpp b/src/desktopfileparser.cpp index 2bb2092..e8327aa 100644 --- a/src/desktopfileparser.cpp +++ b/src/desktopfileparser.cpp @@ -116,7 +116,7 @@ ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexce auto valueStr = line.sliced(splitCharIndex + 1).trimmed(); QString key{""}; - QString localeStr{defaultKeyStr}; + QString localeStr{DesktopFileDefaultKeyLocale}; // NOTE: // We are process "localized keys" here, for usage check: // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys @@ -146,7 +146,7 @@ ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexce return ParserError::NoError; } - if (localeStr != defaultKeyStr and !isInvalidLocaleString(localeStr)) { + if (localeStr != DesktopFileDefaultKeyLocale and !isInvalidLocaleString(localeStr)) { qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr); return ParserError::NoError; } @@ -180,3 +180,47 @@ ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexce return ParserError::NoError; } + +QString toString(const DesktopFileParser::Groups &map) +{ + QString ret; + auto groupToString = [&ret, map](const QString &group) { + const auto &groupEntry = map[group]; + ret.append('[' % group % "]\n"); + for (auto entryIt = groupEntry.constKeyValueBegin(); entryIt != groupEntry.constKeyValueEnd(); ++entryIt) { + const auto &key = entryIt->first; + const auto &value = entryIt->second; + if (value.canConvert()) { + const auto &rawMap = value.value(); + std::for_each(rawMap.constKeyValueBegin(), rawMap.constKeyValueEnd(), [key, &ret](const auto &inner) { + const auto &[locale, rawVal] = inner; + ret.append(key); + if (locale != DesktopFileDefaultKeyLocale) { + ret.append('[' % locale % ']'); + } + ret.append('=' % rawVal % '\n'); + }); + } else if (value.canConvert()) { + const auto &rawVal = value.value(); + auto str = rawVal.join(';'); + ret.append(key % '=' % str % '\n'); + } else if (value.canConvert()) { + const auto &rawVal = value.value(); + ret.append(key % '=' % rawVal % '\n'); + } else { + qWarning() << "value type mismatch:" << value; + } + } + ret.append('\n'); + }; + + groupToString(DesktopFileEntryKey); + for (const auto &groupName : map.keys()) { + if (groupName == DesktopFileEntryKey) { + continue; + } + groupToString(groupName); + } + + return ret; +} diff --git a/src/desktopfileparser.h b/src/desktopfileparser.h index a0d0666..8d9571f 100644 --- a/src/desktopfileparser.h +++ b/src/desktopfileparser.h @@ -17,4 +17,6 @@ public: ParserError addEntry(Groups::iterator &group) noexcept override; }; +QString toString(const DesktopFileParser::Groups &map); + #endif diff --git a/src/global.h b/src/global.h index f9355ff..f6986a1 100644 --- a/src/global.h +++ b/src/global.h @@ -30,7 +30,7 @@ Q_DECLARE_LOGGING_CATEGORY(DDEAMProf) using ObjectInterfaceMap = QMap; using ObjectMap = QMap; using QStringMap = QMap; -using PropMap = QVariantMap; +using PropMap = QMap; Q_DECLARE_METATYPE(ObjectInterfaceMap) Q_DECLARE_METATYPE(ObjectMap) @@ -44,6 +44,21 @@ struct SystemdUnitDBusMessage QDBusObjectPath objectPath; }; +inline const QDBusArgument &operator>>(const QDBusArgument &argument, QStringMap &map) +{ + argument.beginMap(); + while (!argument.atEnd()) { + QString key; + QString value; + argument.beginMapEntry(); + argument >> key >> value; + argument.endMapEntry(); + map.insert(key, value); + } + argument.endMap(); + return argument; +} + inline const QDBusArgument &operator>>(const QDBusArgument &argument, QList &units) { argument.beginArray(); diff --git a/tests/ut_desktopfilegenerator.cpp b/tests/ut_desktopfilegenerator.cpp new file mode 100644 index 0000000..9acf7ba --- /dev/null +++ b/tests/ut_desktopfilegenerator.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "desktopfilegenerator.h" +#include +#include + +TEST(DesktopFileGenerator, generate) +{ + QVariantMap map; + map.insert("Type", QString{"Application"}); + map.insert("Name", + QVariant::fromValue(QStringMap{{"default", "UserApp"}, {"zh_CN", "yonghuyingyong"}, {"en_US", "setApplication"}})); + + map.insert("Actions", QStringList{"one", "two"}); + map.insert("Exec", + QVariant::fromValue(QStringMap{ + {"default", "/usr/bin/exec"}, {"one", "/usr/bin/exec --type=one"}, {"two", "/usr/bin/exec --type=two"}})); + map.insert("Icon", QVariant::fromValue(QStringMap{{"default", "default-icon"}, {"one", "one-icon"}, {"two", "two-icon"}})); + map.insert("ActionName", + QVariantMap{{"one", QVariant::fromValue(QStringMap{{"default", "oneName"}, {"zh_CN", "yi"}, {"en_US", "one"}})}, + {"two", QVariant::fromValue(QStringMap{{"default", "twoname"}, {"zh_CN", "er"}, {"en_US", "two"}})}}); + map.insert("Version", 1.0); + map.insert("Terminal", false); + map.insert("MimeType", QStringList{"text/html", "text/xml", "application/xhtml+xml"}); + + QString errMsg{"NO ERROR"}; + auto content = DesktopFileGenerator::generate(map, errMsg); + EXPECT_EQ(errMsg.toStdString(), QString{"NO ERROR"}.toStdString()); + + QString expect{R"([Desktop Entry] +Actions=one;two +Exec=/usr/bin/exec +Icon=default-icon +MimeType=text/html;text/xml;application/xhtml+xml +Name=UserApp +Name[en_US]=setApplication +Name[zh_CN]=yonghuyingyong +Terminal=false +Type=Application +Version=1 +X-Deepin-CreateBy=dde-application-manager + +[Desktop Action one] +Exec=/usr/bin/exec --type=one +Icon=one-icon +Name=oneName +Name[en_US]=one +Name[zh_CN]=yi + +[Desktop Action two] +Exec=/usr/bin/exec --type=two +Icon=two-icon +Name=twoname +Name[en_US]=two +Name[zh_CN]=er + +)"}; + EXPECT_EQ(expect.toStdString(), content.toStdString()); +}