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 <heyuming@deepin.org>
This commit is contained in:
parent
b71ceb5fc1
commit
1f73eea404
@ -44,7 +44,7 @@
|
||||
<property name="Terminal" type="b" access="read">
|
||||
<annotation
|
||||
name="org.freedesktop.DBus.Description"
|
||||
value="Indicate this application should launch by DEFAULT terminal or not."
|
||||
value="Indicate this application should launch by terminal or not."
|
||||
/>
|
||||
</property>
|
||||
|
||||
@ -56,10 +56,10 @@
|
||||
/>
|
||||
</property>
|
||||
|
||||
<property name="ActionName" type="a{ss}" access="read">
|
||||
<property name="ActionName" type="a{sa{ss}}" access="read">
|
||||
<annotation
|
||||
name="org.freedesktop.DBus.Description"
|
||||
value="The type of ActionName is a Map, where the key represents the locale and the value is the corresponding content."
|
||||
value="The type of ActionName is a Map, first key represents action, second key represents locale and the value is the corresponding content."
|
||||
/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PropMap"/>
|
||||
</property>
|
||||
@ -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."
|
||||
/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PropMap"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QStringMap"/>
|
||||
</property>
|
||||
|
||||
<property name="Name" type="a{ss}" access="read">
|
||||
@ -77,7 +77,7 @@
|
||||
name="org.freedesktop.DBus.Description"
|
||||
value="The meaning of this property's type is same as which in ActionName."
|
||||
/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PropMap"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QStringMap"/>
|
||||
</property>
|
||||
|
||||
<property name="GenericName" type="a{ss}" access="read">
|
||||
@ -85,7 +85,7 @@
|
||||
name="org.freedesktop.DBus.Description"
|
||||
value="The meaning of this property's type is same as which in ActionName."
|
||||
/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PropMap"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QStringMap"/>
|
||||
</property>
|
||||
|
||||
<method name="Launch">
|
||||
|
@ -30,5 +30,20 @@
|
||||
1. You should use pidfd_open(2) to get a pidfd."
|
||||
/>
|
||||
</method>
|
||||
<method name="addUserApplication">
|
||||
<arg type="a{sv}" name="desktop_file" direction="in"/>
|
||||
<arg type="s" name="name" direction="in"/>
|
||||
<arg type="s" name="app_id" direction="out" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap" />
|
||||
<annotation
|
||||
name="org.freedesktop.DBus.Description"
|
||||
value="Desktop-entry-spec: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html,
|
||||
type of `v` is depends on the property which you want to set.
|
||||
examples:
|
||||
{'Name':{'en_US':'example','default':'测试'},{'custom':10}} : a{sa{sv}} // Name=测试 Name[en_US]=example custom=10
|
||||
{'custom':20,'Name':'example'} : a{sv} // custom=20 Name=example
|
||||
"
|
||||
/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
|
@ -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";
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "systemdsignaldispatcher.h"
|
||||
#include "propertiesForwarder.h"
|
||||
#include "applicationHooks.h"
|
||||
#include "desktopfilegenerator.h"
|
||||
#include <QFile>
|
||||
#include <QDBusMessage>
|
||||
#include <unistd.h>
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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<QStringMap>());
|
||||
}
|
||||
|
||||
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<QStringMap>()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return value->value<QStringMap>();
|
||||
}
|
||||
|
||||
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<QStringMap>()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return value->value<QStringMap>();
|
||||
}
|
||||
|
||||
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<QString>());
|
||||
}
|
||||
|
||||
auto mainIcon = m_entry->value(DesktopFileEntryKey, "Icon");
|
||||
if (mainIcon.has_value()) {
|
||||
ret.insert(defaultKeyStr, {std::move(mainIcon).value()});
|
||||
ret.insert(DesktopFileEntryKey, mainIcon->value<QString>());
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -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;
|
||||
|
@ -285,7 +285,7 @@ QString toString(const DesktopEntry::Value &value) noexcept
|
||||
QString str;
|
||||
|
||||
if (value.canConvert<QStringMap>()) { // get default locale
|
||||
str = value.value<QStringMap>()[defaultKeyStr];
|
||||
str = value.value<QStringMap>()[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
|
||||
|
@ -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<QString, QMap<QString, Value>> m_entryMap;
|
||||
bool m_parsed{false};
|
||||
|
||||
public:
|
||||
using container_type = decltype(m_entryMap);
|
||||
};
|
||||
|
||||
bool operator==(const DesktopEntry &lhs, const DesktopEntry &rhs);
|
||||
|
183
src/desktopfilegenerator.cpp
Normal file
183
src/desktopfilegenerator.cpp
Normal file
@ -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<QString>(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<QStringMap>(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<QStringMap>(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<QStringMap>(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<QVariantMap>(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<QStringMap>(*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<QStringMap>(*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<QStringMap>(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;
|
||||
}
|
24
src/desktopfilegenerator.h
Normal file
24
src/desktopfilegenerator.h
Normal file
@ -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
|
@ -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<QStringMap>()) {
|
||||
const auto &rawMap = value.value<QStringMap>();
|
||||
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<QStringList>()) {
|
||||
const auto &rawVal = value.value<QStringList>();
|
||||
auto str = rawVal.join(';');
|
||||
ret.append(key % '=' % str % '\n');
|
||||
} else if (value.canConvert<QString>()) {
|
||||
const auto &rawVal = value.value<QString>();
|
||||
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;
|
||||
}
|
||||
|
@ -17,4 +17,6 @@ public:
|
||||
ParserError addEntry(Groups::iterator &group) noexcept override;
|
||||
};
|
||||
|
||||
QString toString(const DesktopFileParser::Groups &map);
|
||||
|
||||
#endif
|
||||
|
17
src/global.h
17
src/global.h
@ -30,7 +30,7 @@ Q_DECLARE_LOGGING_CATEGORY(DDEAMProf)
|
||||
using ObjectInterfaceMap = QMap<QString, QVariantMap>;
|
||||
using ObjectMap = QMap<QDBusObjectPath, ObjectInterfaceMap>;
|
||||
using QStringMap = QMap<QString, QString>;
|
||||
using PropMap = QVariantMap;
|
||||
using PropMap = QMap<QString, QStringMap>;
|
||||
|
||||
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<SystemdUnitDBusMessage> &units)
|
||||
{
|
||||
argument.beginArray();
|
||||
|
61
tests/ut_desktopfilegenerator.cpp
Normal file
61
tests/ut_desktopfilegenerator.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "desktopfilegenerator.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <QVariant>
|
||||
|
||||
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());
|
||||
}
|
Loading…
Reference in New Issue
Block a user