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">
|
<property name="Terminal" type="b" access="read">
|
||||||
<annotation
|
<annotation
|
||||||
name="org.freedesktop.DBus.Description"
|
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>
|
</property>
|
||||||
|
|
||||||
@ -56,10 +56,10 @@
|
|||||||
/>
|
/>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property name="ActionName" type="a{ss}" access="read">
|
<property name="ActionName" type="a{sa{ss}}" access="read">
|
||||||
<annotation
|
<annotation
|
||||||
name="org.freedesktop.DBus.Description"
|
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"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PropMap"/>
|
||||||
</property>
|
</property>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
name="org.freedesktop.DBus.Description"
|
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."
|
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>
|
||||||
|
|
||||||
<property name="Name" type="a{ss}" access="read">
|
<property name="Name" type="a{ss}" access="read">
|
||||||
@ -77,7 +77,7 @@
|
|||||||
name="org.freedesktop.DBus.Description"
|
name="org.freedesktop.DBus.Description"
|
||||||
value="The meaning of this property's type is same as which in ActionName."
|
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>
|
||||||
|
|
||||||
<property name="GenericName" type="a{ss}" access="read">
|
<property name="GenericName" type="a{ss}" access="read">
|
||||||
@ -85,7 +85,7 @@
|
|||||||
name="org.freedesktop.DBus.Description"
|
name="org.freedesktop.DBus.Description"
|
||||||
value="The meaning of this property's type is same as which in ActionName."
|
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>
|
||||||
|
|
||||||
<method name="Launch">
|
<method name="Launch">
|
||||||
|
@ -30,5 +30,20 @@
|
|||||||
1. You should use pidfd_open(2) to get a pidfd."
|
1. You should use pidfd_open(2) to get a pidfd."
|
||||||
/>
|
/>
|
||||||
</method>
|
</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>
|
</interface>
|
||||||
</node>
|
</node>
|
||||||
|
@ -21,6 +21,7 @@ constexpr auto DDEApplicationManager1JobManager1ObjectPath = u8"/org/desktopspec
|
|||||||
constexpr auto DDEApplicationManager1MimeManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/MimeManager1";
|
constexpr auto DDEApplicationManager1MimeManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/MimeManager1";
|
||||||
constexpr auto DesktopFileEntryKey = u8"Desktop Entry";
|
constexpr auto DesktopFileEntryKey = u8"Desktop Entry";
|
||||||
constexpr auto DesktopFileActionKey = u8"Desktop Action ";
|
constexpr auto DesktopFileActionKey = u8"Desktop Action ";
|
||||||
|
constexpr auto DesktopFileDefaultKeyLocale = "default";
|
||||||
|
|
||||||
constexpr auto ApplicationManagerServerDBusName =
|
constexpr auto ApplicationManagerServerDBusName =
|
||||||
#ifdef DDE_AM_USE_DEBUG_DBUS_NAME
|
#ifdef DDE_AM_USE_DEBUG_DBUS_NAME
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "systemdsignaldispatcher.h"
|
#include "systemdsignaldispatcher.h"
|
||||||
#include "propertiesForwarder.h"
|
#include "propertiesForwarder.h"
|
||||||
#include "applicationHooks.h"
|
#include "applicationHooks.h"
|
||||||
|
#include "desktopfilegenerator.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -548,3 +549,67 @@ ApplicationManager1Service::findApplicationsByIds(const QStringList &appIds) con
|
|||||||
|
|
||||||
return ret;
|
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 ApplicationService;
|
||||||
|
|
||||||
class ApplicationManager1Service final : public QObject
|
class ApplicationManager1Service final : public QObject, public QDBusContext
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@ -54,6 +54,7 @@ public Q_SLOTS:
|
|||||||
QDBusObjectPath &instance,
|
QDBusObjectPath &instance,
|
||||||
ObjectInterfaceMap &application_instance_info) const noexcept;
|
ObjectInterfaceMap &application_instance_info) const noexcept;
|
||||||
void ReloadApplications();
|
void ReloadApplications();
|
||||||
|
QString addUserApplication(const QVariantMap &desktop_file, const QString &name) noexcept;
|
||||||
[[nodiscard]] ObjectMap GetManagedObjects() const;
|
[[nodiscard]] ObjectMap GetManagedObjects() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
@ -384,59 +384,64 @@ QStringList ApplicationService::categories() const noexcept
|
|||||||
PropMap ApplicationService::actionName() const noexcept
|
PropMap ApplicationService::actionName() const noexcept
|
||||||
{
|
{
|
||||||
PropMap ret;
|
PropMap ret;
|
||||||
auto actionList = actions();
|
const auto &actionList = actions();
|
||||||
|
|
||||||
for (auto &action : actionList) {
|
for (const auto &action : actionList) {
|
||||||
action.prepend(DesktopFileActionKey);
|
auto rawActionKey = DesktopFileActionKey % action;
|
||||||
auto value = m_entry->value(action, "Name");
|
auto value = m_entry->value(rawActionKey, "Name");
|
||||||
if (!value.has_value()) {
|
if (!value.has_value()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ret.insert(action, std::move(value).value());
|
ret.insert(action, std::move(value).value().value<QStringMap>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PropMap ApplicationService::name() const noexcept
|
QStringMap ApplicationService::name() const noexcept
|
||||||
{
|
{
|
||||||
PropMap ret;
|
|
||||||
auto value = m_entry->value(DesktopFileEntryKey, "Name");
|
auto value = m_entry->value(DesktopFileEntryKey, "Name");
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return ret;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.insert(QString{"Name"}, {std::move(value).value()});
|
if (!value->canConvert<QStringMap>()) {
|
||||||
return ret;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
PropMap ApplicationService::genericName() const noexcept
|
return value->value<QStringMap>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringMap ApplicationService::genericName() const noexcept
|
||||||
{
|
{
|
||||||
PropMap ret;
|
|
||||||
auto value = m_entry->value(DesktopFileEntryKey, "GenericName");
|
auto value = m_entry->value(DesktopFileEntryKey, "GenericName");
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return ret;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.insert(QString{"GenericName"}, {std::move(value).value()});
|
if (!value->canConvert<QStringMap>()) {
|
||||||
return ret;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
PropMap ApplicationService::icons() const noexcept
|
return value->value<QStringMap>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringMap ApplicationService::icons() const noexcept
|
||||||
{
|
{
|
||||||
PropMap ret;
|
QStringMap ret;
|
||||||
auto actionList = actions();
|
auto actionList = actions();
|
||||||
for (const auto &action : actionList) {
|
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()) {
|
if (!value.has_value()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ret.insert(action, {std::move(value).value()});
|
ret.insert(actionKey, value->value<QString>());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mainIcon = m_entry->value(DesktopFileEntryKey, "Icon");
|
auto mainIcon = m_entry->value(DesktopFileEntryKey, "Icon");
|
||||||
if (mainIcon.has_value()) {
|
if (mainIcon.has_value()) {
|
||||||
ret.insert(defaultKeyStr, {std::move(mainIcon).value()});
|
ret.insert(DesktopFileEntryKey, mainIcon->value<QString>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -50,14 +50,14 @@ public:
|
|||||||
Q_PROPERTY(QString ID READ id CONSTANT)
|
Q_PROPERTY(QString ID READ id CONSTANT)
|
||||||
[[nodiscard]] QString id() const noexcept;
|
[[nodiscard]] QString id() const noexcept;
|
||||||
|
|
||||||
Q_PROPERTY(PropMap Name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QStringMap Name READ name NOTIFY nameChanged)
|
||||||
[[nodiscard]] PropMap name() const noexcept;
|
[[nodiscard]] QStringMap name() const noexcept;
|
||||||
|
|
||||||
Q_PROPERTY(PropMap GenericName READ genericName NOTIFY genericNameChanged)
|
Q_PROPERTY(QStringMap GenericName READ genericName NOTIFY genericNameChanged)
|
||||||
[[nodiscard]] PropMap genericName() const noexcept;
|
[[nodiscard]] QStringMap genericName() const noexcept;
|
||||||
|
|
||||||
Q_PROPERTY(PropMap Icons READ icons NOTIFY iconsChanged)
|
Q_PROPERTY(QStringMap Icons READ icons NOTIFY iconsChanged)
|
||||||
[[nodiscard]] PropMap icons() const noexcept;
|
[[nodiscard]] QStringMap icons() const noexcept;
|
||||||
|
|
||||||
Q_PROPERTY(qulonglong LastLaunchedTime READ lastLaunchedTime NOTIFY lastLaunchedTimeChanged)
|
Q_PROPERTY(qulonglong LastLaunchedTime READ lastLaunchedTime NOTIFY lastLaunchedTimeChanged)
|
||||||
[[nodiscard]] qulonglong lastLaunchedTime() const noexcept;
|
[[nodiscard]] qulonglong lastLaunchedTime() const noexcept;
|
||||||
|
@ -285,7 +285,7 @@ QString toString(const DesktopEntry::Value &value) noexcept
|
|||||||
QString str;
|
QString str;
|
||||||
|
|
||||||
if (value.canConvert<QStringMap>()) { // get default locale
|
if (value.canConvert<QStringMap>()) { // get default locale
|
||||||
str = value.value<QStringMap>()[defaultKeyStr];
|
str = value.value<QStringMap>()[DesktopFileDefaultKeyLocale];
|
||||||
} else {
|
} else {
|
||||||
str = value.toString();
|
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
|
QString toIconString(const DesktopEntry::Value &value) noexcept
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
#include "iniParser.h"
|
#include "iniParser.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
constexpr static auto defaultKeyStr = "default";
|
|
||||||
|
|
||||||
enum class EntryContext { Unknown, EntryOuter, Entry, Done };
|
enum class EntryContext { Unknown, EntryOuter, Entry, Done };
|
||||||
|
|
||||||
enum class EntryValueType { String, LocaleString, Boolean, IconString };
|
enum class EntryValueType { String, LocaleString, Boolean, IconString };
|
||||||
@ -126,6 +124,9 @@ private:
|
|||||||
[[nodiscard]] bool checkMainEntryValidation() const noexcept;
|
[[nodiscard]] bool checkMainEntryValidation() const noexcept;
|
||||||
QMap<QString, QMap<QString, Value>> m_entryMap;
|
QMap<QString, QMap<QString, Value>> m_entryMap;
|
||||||
bool m_parsed{false};
|
bool m_parsed{false};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using container_type = decltype(m_entryMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator==(const DesktopEntry &lhs, const DesktopEntry &rhs);
|
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();
|
auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
|
||||||
|
|
||||||
QString key{""};
|
QString key{""};
|
||||||
QString localeStr{defaultKeyStr};
|
QString localeStr{DesktopFileDefaultKeyLocale};
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// We are process "localized keys" here, for usage check:
|
// We are process "localized keys" here, for usage check:
|
||||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
|
// 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;
|
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);
|
qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
|
||||||
return ParserError::NoError;
|
return ParserError::NoError;
|
||||||
}
|
}
|
||||||
@ -180,3 +180,47 @@ ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexce
|
|||||||
|
|
||||||
return ParserError::NoError;
|
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;
|
ParserError addEntry(Groups::iterator &group) noexcept override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString toString(const DesktopFileParser::Groups &map);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
17
src/global.h
17
src/global.h
@ -30,7 +30,7 @@ Q_DECLARE_LOGGING_CATEGORY(DDEAMProf)
|
|||||||
using ObjectInterfaceMap = QMap<QString, QVariantMap>;
|
using ObjectInterfaceMap = QMap<QString, QVariantMap>;
|
||||||
using ObjectMap = QMap<QDBusObjectPath, ObjectInterfaceMap>;
|
using ObjectMap = QMap<QDBusObjectPath, ObjectInterfaceMap>;
|
||||||
using QStringMap = QMap<QString, QString>;
|
using QStringMap = QMap<QString, QString>;
|
||||||
using PropMap = QVariantMap;
|
using PropMap = QMap<QString, QStringMap>;
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ObjectInterfaceMap)
|
Q_DECLARE_METATYPE(ObjectInterfaceMap)
|
||||||
Q_DECLARE_METATYPE(ObjectMap)
|
Q_DECLARE_METATYPE(ObjectMap)
|
||||||
@ -44,6 +44,21 @@ struct SystemdUnitDBusMessage
|
|||||||
QDBusObjectPath objectPath;
|
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)
|
inline const QDBusArgument &operator>>(const QDBusArgument &argument, QList<SystemdUnitDBusMessage> &units)
|
||||||
{
|
{
|
||||||
argument.beginArray();
|
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