feat: add mimeManager Service
Signed-off-by: ComixHe <heyuming@deepin.org>
This commit is contained in:
parent
8970298ad0
commit
f63741b023
@ -11,6 +11,7 @@
|
||||
<property name="X_Flatpak" type="b" access="read"/>
|
||||
<property name="installedTime" type="t" access="read"/>
|
||||
<property name="NoDisplay" type="b" access="read"/>
|
||||
<property name="MimeTypes" type="as" access="readwrite"/>
|
||||
|
||||
<property name="Actions" type="as" access="read">
|
||||
<annotation
|
||||
|
26
api/dbus/org.desktopspec.MimeManager1.xml
Normal file
26
api/dbus/org.desktopspec.MimeManager1.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.desktopspec.MimeManager1">
|
||||
|
||||
<method name="queryFileTypeAndDefaultApplication">
|
||||
<arg type="s" name="filePath" direction="in"/>
|
||||
<arg type="s" name="mimeType" direction="out"/>
|
||||
<arg type="o" name="application" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="setDefaultApplication">
|
||||
<arg type="a{ss}" name="defaultApps" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="KVPairs"/>
|
||||
</method>
|
||||
|
||||
<method name="unsetDefaultApplication">
|
||||
<arg type="as" name="mimeTypes" direction="in"/>
|
||||
</method>
|
||||
|
||||
<method name="listApplications">
|
||||
<arg type="s" name="mimeType" direction="in"/>
|
||||
<arg name="applications_and_properties" type="a{oa{sa{sv}}}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ObjectMap"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -21,7 +21,7 @@ void registerComplexDbusType()
|
||||
qDBusRegisterMetaType<ObjectInterfaceMap>();
|
||||
qRegisterMetaType<ObjectMap>();
|
||||
qDBusRegisterMetaType<ObjectMap>();
|
||||
qDBusRegisterMetaType<QMap<QString, QString>>();
|
||||
qDBusRegisterMetaType<KVPairs>();
|
||||
qRegisterMetaType<PropMap>();
|
||||
qDBusRegisterMetaType<PropMap>();
|
||||
qDBusRegisterMetaType<QDBusObjectPath>();
|
||||
|
@ -9,9 +9,9 @@ bool registerObjectToDBus(QObject *o, const QString &path, const QString &interf
|
||||
auto &con = ApplicationManager1DBus::instance().globalServerBus();
|
||||
if (!con.registerObject(path, interface, o, QDBusConnection::RegisterOption::ExportAdaptors)) {
|
||||
qCritical() << "register object failed:" << path << interface << con.lastError();
|
||||
} else {
|
||||
qDebug() << "register object:" << path << interface;
|
||||
return false;
|
||||
}
|
||||
qDebug() << "register object:" << path << interface;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
363
src/applicationmimeinfo.cpp
Normal file
363
src/applicationmimeinfo.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "applicationmimeinfo.h"
|
||||
#include "global.h"
|
||||
|
||||
constexpr decltype(auto) desktopSuffix = u8".desktop";
|
||||
|
||||
QStringList getListFiles() noexcept
|
||||
{
|
||||
QStringList files;
|
||||
auto configDirs = getXDGConfigDirs();
|
||||
auto dataDirs = getXDGDataDirs();
|
||||
auto desktop = getCurrentDesktop().toLower();
|
||||
|
||||
auto appendListFile = [&files, &desktop](const QString &dir) {
|
||||
QFileInfo cur{dir};
|
||||
if (!cur.exists() or !cur.isDir()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDir tmp{cur.absoluteFilePath()};
|
||||
auto desktopList = tmp.filePath(QString{"%1-mimeapps.list"}.arg(desktop));
|
||||
if (QFileInfo::exists(desktopList)) {
|
||||
files.append(desktopList);
|
||||
}
|
||||
|
||||
desktopList = tmp.filePath("mimeapps.list");
|
||||
if (QFileInfo::exists(desktopList)) {
|
||||
files.append(desktopList);
|
||||
}
|
||||
};
|
||||
|
||||
std::for_each(configDirs.cbegin(), configDirs.cend(), appendListFile);
|
||||
std::for_each(dataDirs.cbegin(), dataDirs.cend(), appendListFile);
|
||||
std::reverse(files.begin(), files.end());
|
||||
return files;
|
||||
}
|
||||
namespace {
|
||||
QString toString(const MimeContent &content) noexcept
|
||||
{
|
||||
QString ret;
|
||||
|
||||
for (auto it = content.constKeyValueBegin(); it != content.constKeyValueEnd(); ++it) {
|
||||
ret.append(QString{"[%1]\n"}.arg(it->first));
|
||||
const auto &kvPairs = it->second;
|
||||
for (auto inner = kvPairs.constKeyValueBegin(); inner != kvPairs.constKeyValueEnd(); ++inner) {
|
||||
ret.append(QString{"%1="}.arg(inner->first));
|
||||
for (const auto &app : inner->second) {
|
||||
ret.append(QString{"%2;"}.arg(app));
|
||||
}
|
||||
ret.append('\n');
|
||||
}
|
||||
ret.append('\n');
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString removeDesktopSuffix(const QString &str) noexcept
|
||||
{
|
||||
return str.chopped(sizeof(desktopSuffix) - 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<MimeFileBase> MimeFileBase::loadFromFile(const QFileInfo &fileInfo, bool desktopSpec)
|
||||
{
|
||||
bool isWritable{false};
|
||||
auto filePath = fileInfo.absoluteFilePath();
|
||||
if (filePath.startsWith(getXDGConfigHome()) or filePath.startsWith(getXDGDataHome())) {
|
||||
isWritable = true;
|
||||
}
|
||||
|
||||
QFile file{filePath};
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text | QFile::ExistingOnly)) {
|
||||
qWarning() << "open" << filePath << "failed:" << file.errorString();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QTextStream stream{&file};
|
||||
stream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
MimeContent content;
|
||||
MimeFileParser parser{stream, desktopSpec};
|
||||
if (auto err = parser.parse(content); err != ParserError::NoError) {
|
||||
qWarning() << "file:" << filePath << "parse failed:" << err;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (content.isEmpty()) {
|
||||
qInfo() << "ignore empty file:" << filePath;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return MimeFileBase{fileInfo, std::move(content), desktopSpec, isWritable};
|
||||
}
|
||||
|
||||
MimeFileBase::MimeFileBase(const QFileInfo &info, MimeContent &&content, bool desktopSpec, bool writable)
|
||||
: m_desktopSpec(desktopSpec)
|
||||
, m_writable(writable)
|
||||
, m_info(info)
|
||||
, m_content(std::move(content))
|
||||
{
|
||||
}
|
||||
|
||||
void MimeFileBase::reload() noexcept
|
||||
{
|
||||
auto newBase = MimeFileBase::loadFromFile(fileInfo(), isDesktopSpecific());
|
||||
if (!newBase) {
|
||||
qWarning() << "reload" << fileInfo().absoluteFilePath() << "failed, content wouldn't be changed.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_content = std::move(newBase->m_content);
|
||||
}
|
||||
|
||||
MimeApps::MimeApps(MimeFileBase &&base)
|
||||
: MimeFileBase(std::move(base))
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<MimeApps> MimeApps::createMimeApps(const QString &filePath, bool desktopSpec) noexcept
|
||||
{
|
||||
auto baseOpt = MimeFileBase::loadFromFile(QFileInfo{filePath}, desktopSpec);
|
||||
|
||||
if (!baseOpt) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return MimeApps{std::move(baseOpt).value()};
|
||||
}
|
||||
|
||||
void MimeApps::insertToSection(const QString §ion, const QString &mimeType, const QString &appId) noexcept
|
||||
{
|
||||
auto &map = content();
|
||||
|
||||
auto targetSection = map.find(section);
|
||||
if (targetSection == map.end()) {
|
||||
targetSection = map.insert(section, {});
|
||||
}
|
||||
|
||||
QStringList newApps{appId + desktopSuffix};
|
||||
auto oldApps = targetSection->find(mimeType);
|
||||
if (oldApps != targetSection->end()) {
|
||||
newApps.append(*oldApps);
|
||||
}
|
||||
|
||||
targetSection->insert(mimeType, newApps);
|
||||
}
|
||||
|
||||
void MimeApps::addAssociation(const QString &mimeType, const QString &appId) noexcept
|
||||
{
|
||||
insertToSection(addedAssociations, mimeType, appId);
|
||||
}
|
||||
|
||||
void MimeApps::removeAssociation(const QString &mimeType, const QString &appId) noexcept
|
||||
{
|
||||
insertToSection(removedAssociations, mimeType, appId);
|
||||
}
|
||||
|
||||
void MimeApps::setDefaultApplication(const QString &mimeType, const QString &appId) noexcept
|
||||
{
|
||||
auto &map = content();
|
||||
|
||||
auto defaultSection = map.find(defaultApplications);
|
||||
if (defaultSection == map.end()) {
|
||||
defaultSection = map.insert(defaultApplications, {});
|
||||
}
|
||||
|
||||
defaultSection->insert(mimeType, {appId + desktopSuffix});
|
||||
}
|
||||
|
||||
void MimeApps::unsetDefaultApplication(const QString &mimeType) noexcept
|
||||
{
|
||||
auto &map = content();
|
||||
|
||||
auto defaultSection = map.find(defaultApplications);
|
||||
if (defaultSection == map.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
defaultSection->remove(mimeType);
|
||||
}
|
||||
|
||||
AppList MimeApps::queryTypes(QString appId) const noexcept
|
||||
{
|
||||
AppList ret;
|
||||
appId.append(desktopSuffix);
|
||||
const auto &lists = content();
|
||||
|
||||
if (const auto &adds = lists.constFind(addedAssociations); adds != lists.cend()) {
|
||||
std::for_each(adds->constKeyValueBegin(), adds->constKeyValueEnd(), [&ret, &appId](const auto &it) {
|
||||
if (it.second.contains(appId)) {
|
||||
ret.added.append(it.first);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (const auto &removes = lists.constFind(removedAssociations); removes != lists.cend()) {
|
||||
std::for_each(removes->constKeyValueBegin(), removes->constKeyValueEnd(), [&ret, &appId](const auto &it) {
|
||||
if (it.second.contains(appId)) {
|
||||
ret.removed.removeOne(it.first);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MimeApps::writeToFile() const noexcept
|
||||
{
|
||||
auto filePath = fileInfo().absoluteFilePath();
|
||||
if (!isWritable()) {
|
||||
qDebug() << "shouldn't write file:" << filePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file{filePath};
|
||||
|
||||
if (!file.open(QFile::ExistingOnly | QFile::Text | QFile::WriteOnly | QFile::Truncate)) {
|
||||
qWarning() << "open" << filePath << "failed:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto newContent = toString(content()).toLocal8Bit();
|
||||
auto bytes = file.write(newContent);
|
||||
|
||||
if (bytes != newContent.size()) {
|
||||
qWarning() << "incomplete content, write:" << bytes << "total:" << newContent.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MimeApps::queryDefaultApp(const QMimeType &type) const noexcept
|
||||
{
|
||||
const auto &map = content();
|
||||
auto defaultSection = map.find(defaultApplications);
|
||||
if (defaultSection == map.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto defaultApp = defaultSection->find(type.name());
|
||||
if (defaultApp == defaultSection->end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &app = defaultApp.value();
|
||||
if (app.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return removeDesktopSuffix(app.first());
|
||||
}
|
||||
|
||||
QStringList MimeCache::queryTypes(QString appId) const noexcept
|
||||
{
|
||||
QStringList ret;
|
||||
appId.append(desktopSuffix);
|
||||
const auto &cache = content()[mimeCache];
|
||||
for (auto it = cache.constKeyValueBegin(); it != cache.constKeyValueEnd(); ++it) {
|
||||
if (it->second.contains(appId)) {
|
||||
ret.append(it->first);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<MimeCache> MimeCache::createMimeCache(const QString &filePath) noexcept
|
||||
{
|
||||
auto baseOpt = MimeFileBase::loadFromFile(QFileInfo{filePath}, false);
|
||||
|
||||
if (!baseOpt) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return MimeCache{std::move(baseOpt).value()};
|
||||
}
|
||||
|
||||
MimeCache::MimeCache(MimeFileBase &&base)
|
||||
: MimeFileBase(std::move(base))
|
||||
{
|
||||
}
|
||||
|
||||
QStringList MimeCache::queryApps(const QMimeType &type) const noexcept
|
||||
{
|
||||
const auto &content = this->content();
|
||||
auto it = content.constFind(mimeCache);
|
||||
if (it == content.constEnd()) {
|
||||
qDebug() << "this cache is broken, please reload.";
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList ret;
|
||||
if (auto kv = it->constFind(type.name()); kv != it->constEnd()) {
|
||||
const auto &apps = kv.value();
|
||||
for (const auto &e : apps) {
|
||||
if (!e.endsWith(desktopSuffix)) {
|
||||
continue;
|
||||
}
|
||||
ret.append(removeDesktopSuffix(e));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<MimeInfo> MimeInfo::createMimeInfo(const QString &directory) noexcept
|
||||
{
|
||||
MimeInfo ret;
|
||||
auto dirPath = QDir::cleanPath(directory);
|
||||
QDir dir;
|
||||
|
||||
if (!QFileInfo::exists(dirPath)) {
|
||||
qCritical() << "directory " << dirPath << "doesn't exists.";
|
||||
return std::nullopt;
|
||||
}
|
||||
dir.setPath(dirPath);
|
||||
ret.m_directory = dirPath;
|
||||
|
||||
QFileInfo cacheFile{dir.filePath("mimeinfo.cache")};
|
||||
if (cacheFile.exists() and cacheFile.isFile()) {
|
||||
ret.m_cache = MimeCache::createMimeCache(cacheFile.absoluteFilePath());
|
||||
}
|
||||
|
||||
QFileInfo desktopAppList{dir.filePath(QString{"%1-mimeapps.list"}.arg(getCurrentDesktop().toLower()))};
|
||||
if (desktopAppList.exists() and desktopAppList.isFile()) {
|
||||
auto desktopApps = MimeApps::createMimeApps(desktopAppList.absoluteFilePath(), true);
|
||||
if (desktopApps) {
|
||||
ret.m_appsList.emplace_back(std::move(desktopApps).value());
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfo appList{dir.filePath("mimeapps.list")};
|
||||
if (auto userMimeApps = appList.absoluteFilePath();
|
||||
userMimeApps.startsWith(getXDGConfigHome()) and (!appList.exists() or !appList.isFile())) [[unlikely]] {
|
||||
QFile userFile{userMimeApps};
|
||||
if (!userFile.open(QFile::WriteOnly | QFile::Text)) {
|
||||
qCritical() << "failed to create user file:" << userMimeApps << userFile.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
if (appList.exists() and appList.isFile()) {
|
||||
auto apps = MimeApps::createMimeApps(appList.absoluteFilePath(), false);
|
||||
if (apps) {
|
||||
ret.m_appsList.emplace_back(std::move(apps).value());
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MimeInfo::reload() noexcept
|
||||
{
|
||||
for (auto &app : m_appsList) {
|
||||
app.reload();
|
||||
}
|
||||
|
||||
if (m_cache) {
|
||||
m_cache->reload();
|
||||
}
|
||||
}
|
123
src/applicationmimeinfo.h
Normal file
123
src/applicationmimeinfo.h
Normal file
@ -0,0 +1,123 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef APPLICATIONMIMEINFO_H
|
||||
#define APPLICATIONMIMEINFO_H
|
||||
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QFile>
|
||||
#include <QMimeData>
|
||||
#include <memory>
|
||||
#include <QSemaphore>
|
||||
#include <optional>
|
||||
#include <QFileInfo>
|
||||
#include "mimefileparser.h"
|
||||
|
||||
using MimeContent = MimeFileParser::Groups;
|
||||
|
||||
QStringList getListFiles() noexcept;
|
||||
|
||||
class MimeFileBase
|
||||
{
|
||||
public:
|
||||
static std::optional<MimeFileBase> loadFromFile(const QFileInfo &fileInfo, bool desktopSpec);
|
||||
MimeFileBase(MimeFileBase &&) = default;
|
||||
MimeFileBase &operator=(MimeFileBase &&) = default;
|
||||
MimeFileBase(const MimeFileBase &) = delete;
|
||||
MimeFileBase &operator=(const MimeFileBase &) = delete;
|
||||
|
||||
virtual ~MimeFileBase() = default;
|
||||
[[nodiscard]] const QFileInfo &fileInfo() const noexcept { return m_info; }
|
||||
[[nodiscard]] MimeContent &content() noexcept { return m_content; }
|
||||
[[nodiscard]] const MimeContent &content() const noexcept { return m_content; }
|
||||
[[nodiscard]] bool isDesktopSpecific() const noexcept { return m_desktopSpec; }
|
||||
[[nodiscard]] bool isWritable() const noexcept { return m_writable; }
|
||||
|
||||
void reload() noexcept;
|
||||
|
||||
private:
|
||||
MimeFileBase(const QFileInfo &info, MimeContent &&content, bool desktopSpec, bool writable);
|
||||
bool m_desktopSpec{false};
|
||||
bool m_writable{false};
|
||||
QFileInfo m_info;
|
||||
MimeContent m_content;
|
||||
};
|
||||
|
||||
struct AppList
|
||||
{
|
||||
QStringList added;
|
||||
QStringList removed;
|
||||
};
|
||||
|
||||
class MimeApps : public MimeFileBase
|
||||
{
|
||||
public:
|
||||
static std::optional<MimeApps> createMimeApps(const QString &filePath, bool desktopSpec) noexcept;
|
||||
|
||||
MimeApps(MimeApps &&) = default;
|
||||
MimeApps &operator=(MimeApps &&) = default;
|
||||
MimeApps(const MimeApps &) = delete;
|
||||
MimeApps &operator=(const MimeApps &) = delete;
|
||||
~MimeApps() override = default;
|
||||
|
||||
void addAssociation(const QString &mimeType, const QString &appId) noexcept;
|
||||
void removeAssociation(const QString &mimeType, const QString &appId) noexcept;
|
||||
void setDefaultApplication(const QString &mimeType, const QString &appId) noexcept;
|
||||
void unsetDefaultApplication(const QString &mimeType) noexcept;
|
||||
|
||||
[[nodiscard]] QString queryDefaultApp(const QMimeType &type) const noexcept;
|
||||
[[nodiscard]] AppList queryTypes(QString appId) const noexcept;
|
||||
[[nodiscard]] bool writeToFile() const noexcept;
|
||||
|
||||
private:
|
||||
void insertToSection(const QString §ion, const QString &mimeType, const QString &appId) noexcept;
|
||||
explicit MimeApps(MimeFileBase &&base);
|
||||
};
|
||||
|
||||
class MimeCache : public MimeFileBase
|
||||
{
|
||||
public:
|
||||
static std::optional<MimeCache> createMimeCache(const QString &filePath) noexcept;
|
||||
|
||||
MimeCache(MimeCache &&) = default;
|
||||
MimeCache &operator=(MimeCache &&) = default;
|
||||
MimeCache(const MimeCache &) = delete;
|
||||
MimeCache &operator=(const MimeCache &) = delete;
|
||||
~MimeCache() override = default;
|
||||
|
||||
[[nodiscard]] QStringList queryApps(const QMimeType &type) const noexcept;
|
||||
[[nodiscard]] QStringList queryTypes(QString appId) const noexcept;
|
||||
|
||||
private:
|
||||
explicit MimeCache(MimeFileBase &&base);
|
||||
};
|
||||
|
||||
class MimeInfo
|
||||
{
|
||||
public:
|
||||
static std::optional<MimeInfo> createMimeInfo(const QString &directory) noexcept;
|
||||
|
||||
MimeInfo(MimeInfo &&) = default;
|
||||
MimeInfo &operator=(MimeInfo &&) = default;
|
||||
MimeInfo(const MimeInfo &) = delete;
|
||||
MimeInfo &operator=(const MimeInfo &) = delete;
|
||||
~MimeInfo() = default;
|
||||
|
||||
[[nodiscard]] std::vector<MimeApps> &appsList() noexcept { return m_appsList; }
|
||||
[[nodiscard]] const std::vector<MimeApps> &appsList() const noexcept { return m_appsList; }
|
||||
[[nodiscard]] std::optional<MimeCache> &cacheInfo() noexcept { return m_cache; }
|
||||
[[nodiscard]] const std::optional<MimeCache> &cacheInfo() const noexcept { return m_cache; }
|
||||
[[nodiscard]] const QString &directory() const noexcept { return m_directory; }
|
||||
|
||||
void reload() noexcept;
|
||||
|
||||
private:
|
||||
MimeInfo() = default;
|
||||
std::vector<MimeApps> m_appsList;
|
||||
std::optional<MimeCache> m_cache{std::nullopt};
|
||||
QString m_directory;
|
||||
};
|
||||
|
||||
#endif
|
@ -17,7 +17,8 @@ constexpr auto DDEApplicationManager1ServiceName =
|
||||
|
||||
constexpr auto DDEApplicationManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1";
|
||||
constexpr auto DDEAutoStartManager1ObjectPath = u8"/org/desktopspec/AutoStartManager1";
|
||||
constexpr auto DDEApplicationManager1JobManagerObjectPath = u8"/org/desktopspec/ApplicationManager1/JobManager1";
|
||||
constexpr auto DDEApplicationManager1JobManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/JobManager1";
|
||||
constexpr auto DDEApplicationManager1MimeManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/MimeManager1";
|
||||
constexpr auto DesktopFileEntryKey = u8"Desktop Entry";
|
||||
constexpr auto DesktopFileActionKey = u8"Desktop Action ";
|
||||
|
||||
@ -36,12 +37,13 @@ constexpr auto ApplicationManagerDestDBusName =
|
||||
#endif
|
||||
|
||||
constexpr auto ObjectManagerInterface = "org.desktopspec.DBus.ObjectManager";
|
||||
constexpr auto JobManagerInterface = "org.desktopspec.JobManager1";
|
||||
constexpr auto JobManager1Interface = "org.desktopspec.JobManager1";
|
||||
constexpr auto JobInterface = "org.desktopspec.JobManager1.Job";
|
||||
constexpr auto ApplicationManagerInterface = "org.desktopspec.ApplicationManager1";
|
||||
constexpr auto ApplicationManager1Interface = "org.desktopspec.ApplicationManager1";
|
||||
constexpr auto InstanceInterface = "org.desktopspec.ApplicationManager1.Instance";
|
||||
constexpr auto ApplicationInterface = "org.desktopspec.ApplicationManager1.Application";
|
||||
constexpr auto PropertiesInterface = u8"org.freedesktop.DBus.Properties";
|
||||
constexpr auto MimeManager1Interface = u8"org.desktopspec.MimeManager1";
|
||||
|
||||
constexpr auto systemdOption = u8"systemd";
|
||||
constexpr auto splitOption = u8"split";
|
||||
|
@ -13,6 +13,7 @@ qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.deskto
|
||||
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.JobManager1.Job.xml dbus/jobservice.h JobService)
|
||||
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.ObjectManager1.xml dbus/applicationmanager1service.h ApplicationManager1Service AMobjectmanager1adaptor AMObjectManagerAdaptor)
|
||||
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.ObjectManager1.xml dbus/applicationservice.h ApplicationService APPobjectmanager1adaptor APPObjectManagerAdaptor)
|
||||
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.MimeManager1.xml dbus/mimemanager1service.h MimeManager1Service)
|
||||
|
||||
target_sources(dde_am_dbus PRIVATE
|
||||
${dde_am_dbus_SOURCE}
|
||||
|
@ -37,17 +37,20 @@ void ApplicationManager1Service::initService(QDBusConnection &connection) noexce
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
if (!registerObjectToDBus(this, DDEApplicationManager1ObjectPath, ApplicationManagerInterface)) {
|
||||
if (!registerObjectToDBus(this, DDEApplicationManager1ObjectPath, ApplicationManager1Interface)) {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
m_jobManager.reset(new (std::nothrow) JobManager1Service(this));
|
||||
|
||||
if (!m_jobManager) {
|
||||
if (m_jobManager.reset(new (std::nothrow) JobManager1Service(this)); !m_jobManager) {
|
||||
qCritical() << "new JobManager failed.";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
if (m_mimeManager.reset(new (std::nothrow) MimeManager1Service(this)); !m_mimeManager) {
|
||||
qCritical() << "new MimeManager failed.";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
auto &dispatcher = SystemdSignalDispatcher::instance();
|
||||
|
||||
connect(&dispatcher, &SystemdSignalDispatcher::SystemdUnitNew, this, &ApplicationManager1Service::addInstanceToApplication);
|
||||
@ -67,6 +70,8 @@ void ApplicationManager1Service::initService(QDBusConnection &connection) noexce
|
||||
qFatal("connect to ApplicationUpdated failed.");
|
||||
}
|
||||
|
||||
scanMimeInfos();
|
||||
|
||||
scanApplications();
|
||||
|
||||
scanInstances();
|
||||
@ -208,6 +213,20 @@ void ApplicationManager1Service::removeInstanceFromApplication(const QString &un
|
||||
});
|
||||
}
|
||||
|
||||
void ApplicationManager1Service::scanMimeInfos() noexcept
|
||||
{
|
||||
QStringList dirs;
|
||||
dirs.append(getXDGConfigDirs());
|
||||
dirs.append(getDesktopFileDirs());
|
||||
|
||||
for (const auto &dir : dirs) {
|
||||
auto info = MimeInfo::createMimeInfo(dir);
|
||||
if (info) {
|
||||
m_mimeManager->appendMimeInfo(std::move(info).value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationManager1Service::scanApplications() noexcept
|
||||
{
|
||||
const auto &desktopFileDirs = getDesktopFileDirs();
|
||||
@ -215,7 +234,7 @@ void ApplicationManager1Service::scanApplications() noexcept
|
||||
applyIteratively(
|
||||
QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
|
||||
[this](const QFileInfo &info) -> bool {
|
||||
DesktopErrorCode err{DesktopErrorCode::NoError};
|
||||
ParserError err{ParserError::NoError};
|
||||
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
||||
if (!ret.has_value()) {
|
||||
qWarning() << "failed to search File:" << err;
|
||||
@ -449,7 +468,7 @@ void ApplicationManager1Service::updateApplication(const QSharedPointer<Applicat
|
||||
}
|
||||
|
||||
auto err = newEntry->parse(desktopFile);
|
||||
if (err != DesktopErrorCode::NoError) {
|
||||
if (err != ParserError::NoError) {
|
||||
qWarning() << "update desktop file failed:" << err << ", content wouldn't change.";
|
||||
return;
|
||||
}
|
||||
@ -475,7 +494,7 @@ void ApplicationManager1Service::ReloadApplications()
|
||||
applyIteratively(
|
||||
QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
|
||||
[this, &apps](const QFileInfo &info) -> bool {
|
||||
DesktopErrorCode err{DesktopErrorCode::NoError};
|
||||
ParserError err{ParserError::NoError};
|
||||
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
||||
if (!ret.has_value()) {
|
||||
return false;
|
||||
@ -488,7 +507,7 @@ void ApplicationManager1Service::ReloadApplications()
|
||||
m_applicationList.cend(),
|
||||
[&file](const QSharedPointer<ApplicationService> &app) { return file.desktopId() == app->id(); });
|
||||
|
||||
if (err != DesktopErrorCode::NoError) {
|
||||
if (err != ParserError::NoError) {
|
||||
qWarning() << "error occurred:" << err << " skip this application.";
|
||||
return false;
|
||||
}
|
||||
@ -515,3 +534,17 @@ ObjectMap ApplicationManager1Service::GetManagedObjects() const
|
||||
{
|
||||
return dumpDBusObject(m_applicationList);
|
||||
}
|
||||
|
||||
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>>
|
||||
ApplicationManager1Service::findApplicationsByIds(const QStringList &appIds) const noexcept
|
||||
{
|
||||
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>> ret;
|
||||
for (auto it = m_applicationList.constKeyValueBegin(); it != m_applicationList.constKeyValueEnd(); ++it) {
|
||||
const auto &ptr = it->second;
|
||||
if (appIds.contains(ptr->id())) {
|
||||
ret.insert(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <QMap>
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "dbus/jobmanager1service.h"
|
||||
#include "dbus/mimemanager1service.h"
|
||||
#include "desktopentry.h"
|
||||
#include "identifier.h"
|
||||
|
||||
@ -38,12 +39,15 @@ public:
|
||||
bool addApplication(DesktopFile desktopFileSource) noexcept;
|
||||
void removeOneApplication(const QDBusObjectPath &application) noexcept;
|
||||
void removeAllApplication() noexcept;
|
||||
|
||||
[[nodiscard]] QMap<QDBusObjectPath, QSharedPointer<ApplicationService>>
|
||||
findApplicationsByIds(const QStringList &appIds) const noexcept;
|
||||
void updateApplication(const QSharedPointer<ApplicationService> &destApp, DesktopFile desktopFile) noexcept;
|
||||
|
||||
[[nodiscard]] JobManager1Service &jobManager() noexcept { return *m_jobManager; }
|
||||
[[nodiscard]] const JobManager1Service &jobManager() const noexcept { return *m_jobManager; }
|
||||
[[nodiscard]] const QStringList &applicationHooks() const noexcept { return m_hookElements; }
|
||||
[[nodiscard]] MimeManager1Service &mimeManager() noexcept { return *m_mimeManager; }
|
||||
[[nodiscard]] const MimeManager1Service &mimeManager() const noexcept { return *m_mimeManager; }
|
||||
|
||||
public Q_SLOTS:
|
||||
QString Identify(const QDBusUnixFileDescriptor &pidfd,
|
||||
@ -60,10 +64,12 @@ Q_SIGNALS:
|
||||
private:
|
||||
std::unique_ptr<Identifier> m_identifier;
|
||||
std::weak_ptr<ApplicationManager1Storage> m_storage;
|
||||
QScopedPointer<MimeManager1Service> m_mimeManager{nullptr};
|
||||
QScopedPointer<JobManager1Service> m_jobManager{nullptr};
|
||||
QStringList m_hookElements;
|
||||
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>> m_applicationList;
|
||||
|
||||
void scanMimeInfos() noexcept;
|
||||
void scanApplications() noexcept;
|
||||
void scanInstances() noexcept;
|
||||
void scanAutoStart() noexcept;
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "dbus/applicationservice.h"
|
||||
#include "APPobjectmanager1adaptor.h"
|
||||
#include "applicationchecker.h"
|
||||
#include "applicationmanager1service.h"
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "propertiesForwarder.h"
|
||||
#include "dbus/instanceadaptor.h"
|
||||
@ -83,7 +82,7 @@ QSharedPointer<ApplicationService> ApplicationService::createApplicationService(
|
||||
std::unique_ptr<DesktopEntry> entry{std::make_unique<DesktopEntry>()};
|
||||
auto error = entry->parse(sourceStream);
|
||||
|
||||
if (error != DesktopErrorCode::NoError) {
|
||||
if (error != ParserError::NoError) {
|
||||
qWarning() << "parse failed:" << error << app->desktopFileSource().sourcePath();
|
||||
return nullptr;
|
||||
}
|
||||
@ -486,6 +485,79 @@ void ApplicationService::setAutoStart(bool autostart) noexcept
|
||||
emit autostartChanged();
|
||||
}
|
||||
|
||||
QStringList ApplicationService::mimeTypes() const noexcept
|
||||
{
|
||||
QStringList ret;
|
||||
const auto &desktopFilePath = m_desktopSource.sourcePath();
|
||||
const auto &cacheList = parent()->mimeManager().infos();
|
||||
auto cache = std::find_if(cacheList.cbegin(), cacheList.cend(), [&desktopFilePath](const MimeInfo &info) {
|
||||
return desktopFilePath.startsWith(info.directory());
|
||||
});
|
||||
|
||||
const auto &info = cache->cacheInfo();
|
||||
if (info) {
|
||||
ret.append(info->queryTypes(id()));
|
||||
}
|
||||
|
||||
AppList tmp;
|
||||
|
||||
for (auto it = cacheList.crbegin(); it != cacheList.crend(); ++it) {
|
||||
const auto &list = it->appsList();
|
||||
std::for_each(list.crbegin(), list.crend(), [&tmp, this](const MimeApps &app) {
|
||||
auto [added, removed] = app.queryTypes(id());
|
||||
tmp.added.append(std::move(added));
|
||||
tmp.removed.append(std::move(removed));
|
||||
});
|
||||
};
|
||||
|
||||
tmp.added.removeDuplicates();
|
||||
tmp.removed.removeDuplicates();
|
||||
for (const auto &it : tmp.removed) {
|
||||
tmp.added.removeOne(it);
|
||||
}
|
||||
|
||||
ret.append(std::move(tmp.added));
|
||||
ret.removeDuplicates();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ApplicationService::setMimeTypes(const QStringList &value) noexcept
|
||||
{
|
||||
auto oldMimes = mimeTypes();
|
||||
auto newMimes = value;
|
||||
std::sort(oldMimes.begin(), oldMimes.end());
|
||||
std::sort(newMimes.begin(), newMimes.end());
|
||||
|
||||
QStringList newAdds;
|
||||
QStringList newRemoved;
|
||||
|
||||
std::set_difference(oldMimes.begin(), oldMimes.end(), newMimes.begin(), newMimes.end(), std::back_inserter(newRemoved));
|
||||
std::set_difference(newMimes.begin(), newMimes.end(), oldMimes.begin(), oldMimes.end(), std::back_inserter(newAdds));
|
||||
|
||||
static QString userDir = getXDGConfigHome();
|
||||
auto &infos = parent()->mimeManager().infos();
|
||||
auto userInfo = std::find_if(infos.begin(), infos.end(), [](const MimeInfo &info) { return info.directory() == userDir; });
|
||||
if (userInfo == infos.cend()) {
|
||||
sendErrorReply(QDBusError::Failed, "user-specific config file doesn't exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &list = userInfo->appsList().rbegin();
|
||||
const auto &appId = id();
|
||||
for (const auto &add : newAdds) {
|
||||
list->addAssociation(add, appId);
|
||||
}
|
||||
for (const auto &remove : newRemoved) {
|
||||
list->removeAssociation(remove, appId);
|
||||
}
|
||||
|
||||
if (!list->writeToFile()) {
|
||||
qWarning() << "error occurred when write mime association to file";
|
||||
}
|
||||
|
||||
emit MimeTypesChanged();
|
||||
}
|
||||
|
||||
QList<QDBusObjectPath> ApplicationService::instances() const noexcept
|
||||
{
|
||||
return m_Instances.keys();
|
||||
@ -567,6 +639,7 @@ void ApplicationService::resetEntry(DesktopEntry *newEntry) noexcept
|
||||
emit actionNameChanged();
|
||||
emit actionsChanged();
|
||||
emit categoriesChanged();
|
||||
emit MimeTypesChanged();
|
||||
}
|
||||
|
||||
LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringList &fields)
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "global.h"
|
||||
#include "desktopentry.h"
|
||||
#include "dbus/jobmanager1service.h"
|
||||
#include "applicationmanager1service.h"
|
||||
|
||||
QString getDeepinWineScaleFactor(const QString &appId) noexcept;
|
||||
|
||||
@ -67,6 +68,10 @@ public:
|
||||
[[nodiscard]] bool isAutoStart() const noexcept;
|
||||
void setAutoStart(bool autostart) noexcept;
|
||||
|
||||
Q_PROPERTY(QStringList MimeTypes READ mimeTypes WRITE setMimeTypes)
|
||||
[[nodiscard]] QStringList mimeTypes() const noexcept;
|
||||
void setMimeTypes(const QStringList &value) noexcept;
|
||||
|
||||
Q_PROPERTY(QList<QDBusObjectPath> Instances READ instances NOTIFY instanceChanged)
|
||||
[[nodiscard]] QList<QDBusObjectPath> instances() const noexcept;
|
||||
|
||||
@ -129,6 +134,7 @@ Q_SIGNALS:
|
||||
void actionNameChanged();
|
||||
void actionsChanged();
|
||||
void categoriesChanged();
|
||||
void MimeTypesChanged();
|
||||
|
||||
private:
|
||||
friend class ApplicationManager1Service;
|
||||
|
@ -9,7 +9,7 @@ JobManager1Service::JobManager1Service(ApplicationManager1Service *parent)
|
||||
: m_parent(parent)
|
||||
{
|
||||
new JobManager1Adaptor{this};
|
||||
if (!registerObjectToDBus(this, DDEApplicationManager1JobManagerObjectPath, JobManagerInterface)) {
|
||||
if (!registerObjectToDBus(this, DDEApplicationManager1JobManager1ObjectPath, JobManager1Interface)) {
|
||||
std::terminate();
|
||||
}
|
||||
qRegisterMetaType<LaunchTask>();
|
||||
|
@ -51,7 +51,7 @@ public:
|
||||
static_assert(std::is_invocable_v<F, QVariant>, "param type must be QVariant.");
|
||||
|
||||
QString objectPath =
|
||||
QString{"%1/%2"}.arg(DDEApplicationManager1JobManagerObjectPath).arg(QUuid::createUuid().toString(QUuid::Id128));
|
||||
QString{"%1/%2"}.arg(DDEApplicationManager1JobManager1ObjectPath).arg(QUuid::createUuid().toString(QUuid::Id128));
|
||||
QFuture<QVariantList> future = QtConcurrent::mappedReduced(std::move(args),
|
||||
func,
|
||||
qOverload<QVariantList::parameter_type>(&QVariantList::append),
|
||||
|
114
src/dbus/mimemanager1service.cpp
Normal file
114
src/dbus/mimemanager1service.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "dbus/mimemanager1adaptor.h"
|
||||
#include "applicationmanager1service.h"
|
||||
#include "applicationservice.h"
|
||||
#include "constant.h"
|
||||
|
||||
MimeManager1Service::MimeManager1Service(ApplicationManager1Service *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
new MimeManager1Adaptor{this};
|
||||
if (!registerObjectToDBus(this, DDEApplicationManager1MimeManager1ObjectPath, MimeManager1Interface)) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
MimeManager1Service::~MimeManager1Service() = default;
|
||||
|
||||
ObjectMap MimeManager1Service::listApplications(const QString &mimeType) const noexcept
|
||||
{
|
||||
auto mime = m_database.mimeTypeForName(mimeType);
|
||||
if (!mime.isValid()) {
|
||||
qWarning() << "can't find" << mimeType;
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList appIds;
|
||||
|
||||
for (auto it = m_infos.rbegin(); it != m_infos.rend(); ++it) {
|
||||
const auto &info = it->cacheInfo();
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
auto apps = info->queryApps(mime);
|
||||
appIds.append(std::move(apps));
|
||||
}
|
||||
appIds.removeDuplicates();
|
||||
qInfo() << "query" << mimeType << "find:" << appIds;
|
||||
const auto &apps = static_cast<ApplicationManager1Service *>(parent())->findApplicationsByIds(appIds);
|
||||
return dumpDBusObject(apps);
|
||||
}
|
||||
|
||||
QString MimeManager1Service::queryFileTypeAndDefaultApplication(const QString &filePath,
|
||||
QDBusObjectPath &application) const noexcept
|
||||
{
|
||||
QString mimeType;
|
||||
application = QDBusObjectPath{"/"};
|
||||
|
||||
auto mime = m_database.mimeTypeForFile(filePath);
|
||||
if (mime.isValid()) {
|
||||
mimeType = mime.name();
|
||||
}
|
||||
|
||||
QString defaultAppId;
|
||||
for (auto it1 = m_infos.rbegin(); it1 != m_infos.rend(); ++it1) {
|
||||
const auto &list = it1->appsList();
|
||||
for (auto it2 = list.rbegin(); it2 != list.rend(); ++it2) {
|
||||
if (auto app = it2->queryDefaultApp(mime); !app.isEmpty()) {
|
||||
defaultAppId = app;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultAppId.isEmpty()) {
|
||||
qInfo() << "file's mimeType:" << mime.name() << "but can't find a default application for this type.";
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
const auto &apps = static_cast<ApplicationManager1Service *>(parent())->findApplicationsByIds({defaultAppId});
|
||||
if (apps.isEmpty()) {
|
||||
qWarning() << "default application has been found:" << defaultAppId
|
||||
<< " but we can't find corresponding application in ApplicationManagerService.";
|
||||
} else {
|
||||
application = apps.firstKey();
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
void MimeManager1Service::setDefaultApplication(const KVPairs &defaultApps) noexcept
|
||||
{
|
||||
auto &app = m_infos.front().appsList();
|
||||
auto userConfig = std::find_if(
|
||||
app.begin(), app.end(), [](const MimeApps &config) { return !config.isDesktopSpecific(); }); // always find this
|
||||
|
||||
for (auto it = defaultApps.constKeyValueBegin(); it != defaultApps.constKeyValueEnd(); ++it) {
|
||||
userConfig->setDefaultApplication(it->first, it->second);
|
||||
}
|
||||
|
||||
if (!userConfig->writeToFile()) {
|
||||
sendErrorReply(QDBusError::Failed, "set default app failed, these config will be reset after re-login.");
|
||||
}
|
||||
}
|
||||
|
||||
void MimeManager1Service::unsetDefaultApplication(const QStringList &mimeTypes) noexcept
|
||||
{
|
||||
auto &app = m_infos.front().appsList();
|
||||
auto userConfig = std::find_if(app.begin(), app.end(), [](const MimeApps &config) { return !config.isDesktopSpecific(); });
|
||||
|
||||
for (const auto &mime : mimeTypes) {
|
||||
userConfig->unsetDefaultApplication(mime);
|
||||
}
|
||||
|
||||
if (!userConfig->writeToFile()) {
|
||||
sendErrorReply(QDBusError::Failed, "unset default app failed, these config will be reset after re-login.");
|
||||
}
|
||||
}
|
||||
|
||||
void MimeManager1Service::appendMimeInfo(MimeInfo &&info)
|
||||
{
|
||||
m_infos.emplace_back(std::move(info));
|
||||
}
|
39
src/dbus/mimemanager1service.h
Normal file
39
src/dbus/mimemanager1service.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef MIMEMANAGERSERVICE_H
|
||||
#define MIMEMANAGERSERVICE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDBusContext>
|
||||
#include <QDBusObjectPath>
|
||||
#include "global.h"
|
||||
#include "applicationmimeinfo.h"
|
||||
|
||||
class ApplicationManager1Service;
|
||||
|
||||
class MimeManager1Service : public QObject, public QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MimeManager1Service(ApplicationManager1Service *parent);
|
||||
~MimeManager1Service() override;
|
||||
|
||||
void appendMimeInfo(MimeInfo &&info);
|
||||
[[nodiscard]] const auto &infos() const noexcept { return m_infos; }
|
||||
[[nodiscard]] auto &infos() noexcept { return m_infos; }
|
||||
|
||||
public Q_SLOTS:
|
||||
[[nodiscard]] ObjectMap listApplications(const QString &mimeType) const noexcept;
|
||||
[[nodiscard]] QString queryFileTypeAndDefaultApplication(const QString &filePath,
|
||||
QDBusObjectPath &application) const noexcept;
|
||||
void setDefaultApplication(const KVPairs &defaultApps) noexcept;
|
||||
void unsetDefaultApplication(const QStringList &mimeTypes) noexcept;
|
||||
|
||||
private:
|
||||
QMimeDatabase m_database;
|
||||
std::vector<MimeInfo> m_infos;
|
||||
};
|
||||
|
||||
#endif
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "desktopentry.h"
|
||||
#include "global.h"
|
||||
#include "desktopfileparser.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <algorithm>
|
||||
@ -15,203 +16,6 @@
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
bool isInvalidLocaleString(const QString &str) noexcept
|
||||
{
|
||||
constexpr auto Language = R"((?:[a-z]+))"; // language of locale postfix. eg.(en, zh)
|
||||
constexpr auto Country = R"((?:_[A-Z]+))"; // country of locale postfix. eg.(US, CN)
|
||||
constexpr auto Encoding = R"((?:\.[0-9A-Z-]+))"; // encoding of locale postfix. eg.(UFT-8)
|
||||
constexpr auto Modifier = R"((?:@[a-zA-Z=;]+))"; // modifier of locale postfix. eg.(euro;collation=traditional)
|
||||
const static auto validKey = QString(R"(^%1%2?%3?%4?$)").arg(Language, Country, Encoding, Modifier);
|
||||
// example: https://regex101.com/r/hylOay/2
|
||||
static const QRegularExpression _re = []() -> QRegularExpression {
|
||||
QRegularExpression tmp{validKey};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto re = _re;
|
||||
|
||||
return re.match(str).hasMatch();
|
||||
}
|
||||
|
||||
bool hasNonAsciiAndControlCharacters(const QString &str) noexcept
|
||||
{
|
||||
static const QRegularExpression _matchControlChars = []() {
|
||||
QRegularExpression tmp{R"(\p{Cc})"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto matchControlChars = _matchControlChars;
|
||||
static const QRegularExpression _matchNonAsciiChars = []() {
|
||||
QRegularExpression tmp{R"([^\x00-\x7f])"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto matchNonAsciiChars = _matchNonAsciiChars;
|
||||
if (str.contains(matchControlChars) and str.contains(matchNonAsciiChars)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct Parser
|
||||
{
|
||||
Parser(QTextStream &stream)
|
||||
: m_stream(stream){};
|
||||
QTextStream &m_stream;
|
||||
QString m_line;
|
||||
|
||||
using Groups = QMap<QString, QMap<QString, DesktopEntry::Value>>;
|
||||
|
||||
DesktopErrorCode parse(Groups &groups) noexcept;
|
||||
|
||||
private:
|
||||
void skip() noexcept;
|
||||
DesktopErrorCode addGroup(Groups &groups) noexcept;
|
||||
DesktopErrorCode addEntry(Groups::iterator &group) noexcept;
|
||||
};
|
||||
|
||||
void Parser::skip() noexcept
|
||||
{
|
||||
while (!m_stream.atEnd() and (m_line.startsWith('#') or m_line.isEmpty())) {
|
||||
m_line = m_stream.readLine().trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
DesktopErrorCode Parser::parse(Groups &ret) noexcept
|
||||
{
|
||||
std::remove_reference_t<decltype(ret)> groups;
|
||||
while (!m_stream.atEnd()) {
|
||||
auto err = addGroup(groups);
|
||||
if (err != DesktopErrorCode::NoError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (groups.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (groups.keys().first() != DesktopFileEntryKey) {
|
||||
qWarning() << "There should be nothing preceding "
|
||||
"'Desktop Entry' group in the desktop entry file "
|
||||
"but possibly one or more comments.";
|
||||
return DesktopErrorCode::InvalidFormat;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_line.isEmpty()) {
|
||||
qCritical() << "Something is wrong in Desktop file parser, check logic.";
|
||||
return DesktopErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ret = std::move(groups);
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
DesktopErrorCode Parser::addGroup(Groups &ret) noexcept
|
||||
{
|
||||
skip();
|
||||
if (!m_line.startsWith('[')) {
|
||||
qWarning() << "Invalid desktop file format: unexpected line:" << m_line;
|
||||
return DesktopErrorCode::InvalidFormat;
|
||||
}
|
||||
|
||||
// Parsing group header.
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#group-header
|
||||
|
||||
auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
|
||||
|
||||
if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
|
||||
qWarning() << "group header invalid:" << m_line;
|
||||
return DesktopErrorCode::InvalidFormat;
|
||||
}
|
||||
|
||||
if (ret.find(groupHeader) != ret.end()) {
|
||||
qWarning() << "duplicated group header detected:" << groupHeader;
|
||||
return DesktopErrorCode::InvalidFormat;
|
||||
}
|
||||
|
||||
auto group = ret.insert(groupHeader, {});
|
||||
|
||||
m_line.clear();
|
||||
while (!m_stream.atEnd() && !m_line.startsWith('[')) {
|
||||
skip();
|
||||
if (m_line.startsWith('[')) {
|
||||
break;
|
||||
}
|
||||
auto err = addEntry(group);
|
||||
if (err != DesktopErrorCode::NoError) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
DesktopErrorCode Parser::addEntry(Groups::iterator &group) noexcept
|
||||
{
|
||||
auto line = m_line;
|
||||
m_line.clear();
|
||||
auto splitCharIndex = line.indexOf('=');
|
||||
if (splitCharIndex == -1) {
|
||||
qWarning() << "invalid line in desktop file, skip it:" << line;
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
auto keyStr = line.first(splitCharIndex).trimmed();
|
||||
auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
|
||||
|
||||
QString key{""};
|
||||
QString localeStr{defaultKeyStr};
|
||||
// NOTE:
|
||||
// We are process "localized keys" here, for usage check:
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
|
||||
qsizetype localeBegin = keyStr.indexOf('[');
|
||||
qsizetype localeEnd = keyStr.lastIndexOf(']');
|
||||
if ((localeBegin == -1 && localeEnd != -1) || (localeBegin != -1 && localeEnd == -1)) {
|
||||
qWarning() << "unmatched [] detected in desktop file, skip this line: " << line;
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (localeBegin == -1 && localeEnd == -1) {
|
||||
key = keyStr;
|
||||
} else {
|
||||
key = keyStr.sliced(0, localeBegin);
|
||||
localeStr = keyStr.sliced(localeBegin + 1, localeEnd - localeBegin - 1); // strip '[' and ']'
|
||||
}
|
||||
|
||||
static const QRegularExpression _re = []() {
|
||||
QRegularExpression tmp{"R([^A-Za-z0-9-])"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
// NOTE: https://stackoverflow.com/a/25583104
|
||||
thread_local const QRegularExpression re = _re;
|
||||
if (re.match(key).hasMatch()) {
|
||||
qWarning() << "invalid key name, skip this line:" << line;
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (localeStr != defaultKeyStr && !isInvalidLocaleString(localeStr)) {
|
||||
qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
|
||||
}
|
||||
|
||||
auto keyIt = group->find(key);
|
||||
if (keyIt != group->end() && keyIt->find(localeStr) != keyIt->end()) {
|
||||
qWarning() << "duplicated localestring, skip this line:" << line;
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (keyIt == group->end()) {
|
||||
group->insert(key, {{localeStr, valueStr}});
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
keyIt->insert(localeStr, valueStr);
|
||||
return DesktopErrorCode::NoError;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString DesktopFile::sourcePath() const noexcept
|
||||
{
|
||||
if (!m_fileSource) {
|
||||
@ -293,20 +97,20 @@ std::optional<DesktopFile> DesktopFile::createTemporaryDesktopFile(const QString
|
||||
return createTemporaryDesktopFile(std::move(tempFile));
|
||||
}
|
||||
|
||||
std::optional<DesktopFile> DesktopFile::searchDesktopFileByPath(const QString &desktopFile, DesktopErrorCode &err) noexcept
|
||||
std::optional<DesktopFile> DesktopFile::searchDesktopFileByPath(const QString &desktopFile, ParserError &err) noexcept
|
||||
{
|
||||
decltype(auto) desktopSuffix = ".desktop";
|
||||
|
||||
if (!desktopFile.endsWith(desktopSuffix)) {
|
||||
qWarning() << "file isn't a desktop file:" << desktopFile;
|
||||
err = DesktopErrorCode::MismatchedFile;
|
||||
err = ParserError::MismatchedFile;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QFileInfo fileinfo{desktopFile};
|
||||
if (!fileinfo.isAbsolute() or !fileinfo.exists()) {
|
||||
qWarning() << "desktop file not found.";
|
||||
err = DesktopErrorCode::NotFound;
|
||||
err = ParserError::NotFound;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -334,11 +138,11 @@ std::optional<DesktopFile> DesktopFile::searchDesktopFileByPath(const QString &d
|
||||
|
||||
auto [ctime, mtime, _] = getFileTimeInfo(QFileInfo{*filePtr});
|
||||
|
||||
err = DesktopErrorCode::NoError;
|
||||
err = ParserError::NoError;
|
||||
return DesktopFile{std::move(filePtr), std::move(id), mtime, ctime};
|
||||
}
|
||||
|
||||
std::optional<DesktopFile> DesktopFile::searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept
|
||||
std::optional<DesktopFile> DesktopFile::searchDesktopFileById(const QString &appId, ParserError &err) noexcept
|
||||
{
|
||||
auto XDGDataDirs = getDesktopFileDirs();
|
||||
constexpr auto desktopSuffix = u8".desktop";
|
||||
@ -360,7 +164,7 @@ std::optional<DesktopFile> DesktopFile::searchDesktopFileById(const QString &app
|
||||
}
|
||||
}
|
||||
|
||||
err = DesktopErrorCode::NotFound;
|
||||
err = ParserError::NotFound;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -369,13 +173,13 @@ bool DesktopFile::modified(qint64 time) const noexcept
|
||||
return time != m_mtime;
|
||||
}
|
||||
|
||||
DesktopErrorCode DesktopEntry::parse(const DesktopFile &file) noexcept
|
||||
ParserError DesktopEntry::parse(const DesktopFile &file) noexcept
|
||||
{
|
||||
DesktopFileGuard guard{file};
|
||||
|
||||
if (!guard.try_open()) {
|
||||
qWarning() << file.sourcePath() << "can't open.";
|
||||
return DesktopErrorCode::OpenFailed;
|
||||
return ParserError::OpenFailed;
|
||||
}
|
||||
|
||||
QTextStream stream;
|
||||
@ -383,29 +187,29 @@ DesktopErrorCode DesktopEntry::parse(const DesktopFile &file) noexcept
|
||||
return parse(stream);
|
||||
}
|
||||
|
||||
DesktopErrorCode DesktopEntry::parse(QTextStream &stream) noexcept
|
||||
ParserError DesktopEntry::parse(QTextStream &stream) noexcept
|
||||
{
|
||||
if (m_parsed == true) {
|
||||
return DesktopErrorCode::Parsed;
|
||||
if (m_parsed) {
|
||||
return ParserError::Parsed;
|
||||
}
|
||||
|
||||
if (stream.atEnd()) {
|
||||
return DesktopErrorCode::OpenFailed;
|
||||
return ParserError::OpenFailed;
|
||||
}
|
||||
|
||||
stream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
DesktopErrorCode err{DesktopErrorCode::NoError};
|
||||
Parser p(stream);
|
||||
ParserError err{ParserError::NoError};
|
||||
DesktopFileParser p(stream);
|
||||
err = p.parse(m_entryMap);
|
||||
m_parsed = true;
|
||||
if (err != DesktopErrorCode::NoError) {
|
||||
if (err != ParserError::NoError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!checkMainEntryValidation()) {
|
||||
qWarning() << "invalid MainEntry, abort.";
|
||||
err = DesktopErrorCode::MissingInfo;
|
||||
err = ParserError::MissingInfo;
|
||||
}
|
||||
|
||||
return err;
|
||||
@ -584,40 +388,3 @@ QDebug operator<<(QDebug debug, const DesktopEntry::Value &v)
|
||||
debug << static_cast<const QMap<QString, QString> &>(v);
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const DesktopErrorCode &v)
|
||||
{
|
||||
QDebugStateSaver saver{debug};
|
||||
QString errMsg;
|
||||
switch (v) {
|
||||
case DesktopErrorCode::NoError: {
|
||||
errMsg = "no error.";
|
||||
} break;
|
||||
case DesktopErrorCode::NotFound: {
|
||||
errMsg = "file not found.";
|
||||
} break;
|
||||
case DesktopErrorCode::MismatchedFile: {
|
||||
errMsg = "file type is mismatched.";
|
||||
} break;
|
||||
case DesktopErrorCode::InvalidLocation: {
|
||||
errMsg = "file location is invalid, please check $XDG_DATA_DIRS.";
|
||||
} break;
|
||||
case DesktopErrorCode::OpenFailed: {
|
||||
errMsg = "couldn't open the file.";
|
||||
} break;
|
||||
case DesktopErrorCode::InvalidFormat: {
|
||||
errMsg = "the format of desktopEntry file is invalid.";
|
||||
} break;
|
||||
case DesktopErrorCode::MissingInfo: {
|
||||
errMsg = "missing required infomation.";
|
||||
} break;
|
||||
case DesktopErrorCode::Parsed: {
|
||||
errMsg = "this desktop entry is parsed.";
|
||||
} break;
|
||||
case DesktopErrorCode::InternalError: {
|
||||
errMsg = "internal error of parser.";
|
||||
} break;
|
||||
}
|
||||
debug << errMsg;
|
||||
return debug;
|
||||
}
|
||||
|
@ -12,21 +12,10 @@
|
||||
#include <QTextStream>
|
||||
#include <optional>
|
||||
#include <QFile>
|
||||
#include "iniParser.h"
|
||||
|
||||
constexpr static auto defaultKeyStr = "default";
|
||||
|
||||
enum class DesktopErrorCode {
|
||||
NoError,
|
||||
NotFound,
|
||||
MismatchedFile,
|
||||
InvalidLocation,
|
||||
InvalidFormat,
|
||||
OpenFailed,
|
||||
MissingInfo,
|
||||
Parsed,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
enum class EntryContext { Unknown, EntryOuter, Entry, Done };
|
||||
|
||||
enum class EntryValueType { String, LocaleString, Boolean, IconString };
|
||||
@ -53,8 +42,8 @@ struct DesktopFile
|
||||
friend bool operator==(const DesktopFile &lhs, const DesktopFile &rhs);
|
||||
friend bool operator!=(const DesktopFile &lhs, const DesktopFile &rhs);
|
||||
|
||||
static std::optional<DesktopFile> searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept;
|
||||
static std::optional<DesktopFile> searchDesktopFileByPath(const QString &desktopFilePath, DesktopErrorCode &err) noexcept;
|
||||
static std::optional<DesktopFile> searchDesktopFileById(const QString &appId, ParserError &err) noexcept;
|
||||
static std::optional<DesktopFile> searchDesktopFileByPath(const QString &desktopFilePath, ParserError &err) noexcept;
|
||||
static std::optional<DesktopFile> createTemporaryDesktopFile(const QString &temporaryFile) noexcept;
|
||||
static std::optional<DesktopFile> createTemporaryDesktopFile(std::unique_ptr<QFile> temporaryFile) noexcept;
|
||||
|
||||
@ -138,8 +127,8 @@ public:
|
||||
DesktopEntry &operator=(DesktopEntry &&) = default;
|
||||
|
||||
~DesktopEntry() = default;
|
||||
[[nodiscard]] DesktopErrorCode parse(const DesktopFile &file) noexcept;
|
||||
[[nodiscard]] DesktopErrorCode parse(QTextStream &stream) noexcept;
|
||||
[[nodiscard]] ParserError parse(const DesktopFile &file) noexcept;
|
||||
[[nodiscard]] ParserError parse(QTextStream &stream) noexcept;
|
||||
[[nodiscard]] std::optional<QMap<QString, Value>> group(const QString &key) const noexcept;
|
||||
[[nodiscard]] std::optional<Value> value(const QString &key, const QString &valueKey) const noexcept;
|
||||
|
||||
@ -154,8 +143,6 @@ private:
|
||||
|
||||
QDebug operator<<(QDebug debug, const DesktopEntry::Value &v);
|
||||
|
||||
QDebug operator<<(QDebug debug, const DesktopErrorCode &v);
|
||||
|
||||
bool operator==(const DesktopEntry &lhs, const DesktopEntry &rhs);
|
||||
|
||||
bool operator!=(const DesktopEntry &lhs, const DesktopEntry &rhs);
|
||||
|
159
src/desktopfileparser.cpp
Normal file
159
src/desktopfileparser.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include "desktopfileparser.h"
|
||||
#include "constant.h"
|
||||
|
||||
namespace {
|
||||
bool isInvalidLocaleString(const QString &str) noexcept
|
||||
{
|
||||
constexpr auto Language = R"((?:[a-z]+))"; // language of locale postfix. eg.(en, zh)
|
||||
constexpr auto Country = R"((?:_[A-Z]+))"; // country of locale postfix. eg.(US, CN)
|
||||
constexpr auto Encoding = R"((?:\.[0-9A-Z-]+))"; // encoding of locale postfix. eg.(UFT-8)
|
||||
constexpr auto Modifier = R"((?:@[a-zA-Z=;]+))"; // modifier of locale postfix. eg.(euro;collation=traditional)
|
||||
const static auto validKey = QString(R"(^%1%2?%3?%4?$)").arg(Language, Country, Encoding, Modifier);
|
||||
// example: https://regex101.com/r/hylOay/2
|
||||
static const QRegularExpression _re = []() -> QRegularExpression {
|
||||
QRegularExpression tmp{validKey};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto re = _re;
|
||||
|
||||
return re.match(str).hasMatch();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ParserError DesktopFileParser::parse(Groups &ret) noexcept
|
||||
{
|
||||
std::remove_reference_t<decltype(ret)> groups;
|
||||
while (!m_stream.atEnd()) {
|
||||
auto err = addGroup(groups);
|
||||
if (err != ParserError::NoError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (groups.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (groups.keys().first() != DesktopFileEntryKey) {
|
||||
qWarning() << "There should be nothing preceding "
|
||||
"'Desktop Entry' group in the desktop entry file "
|
||||
"but possibly one or more comments.";
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_line.isEmpty()) {
|
||||
qCritical() << "Something is wrong in Desktop file parser, check logic.";
|
||||
return ParserError::InternalError;
|
||||
}
|
||||
|
||||
ret = std::move(groups);
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
ParserError DesktopFileParser::addGroup(Groups &ret) noexcept
|
||||
{
|
||||
skip();
|
||||
if (!m_line.startsWith('[')) {
|
||||
qWarning() << "Invalid desktop file format: unexpected line:" << m_line;
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
// Parsing group header.
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#group-header
|
||||
|
||||
auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
|
||||
|
||||
if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
|
||||
qWarning() << "group header invalid:" << m_line;
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
if (ret.find(groupHeader) != ret.end()) {
|
||||
qWarning() << "duplicated group header detected:" << groupHeader;
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
auto group = ret.insert(groupHeader, {});
|
||||
|
||||
m_line.clear();
|
||||
while (!m_stream.atEnd() && !m_line.startsWith('[')) {
|
||||
skip();
|
||||
if (m_line.startsWith('[')) {
|
||||
break;
|
||||
}
|
||||
auto err = addEntry(group);
|
||||
if (err != ParserError::NoError) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexcept
|
||||
{
|
||||
auto line = m_line;
|
||||
m_line.clear();
|
||||
auto splitCharIndex = line.indexOf('=');
|
||||
if (splitCharIndex == -1) {
|
||||
qWarning() << "invalid line in desktop file, skip it:" << line;
|
||||
return ParserError::NoError;
|
||||
}
|
||||
auto keyStr = line.first(splitCharIndex).trimmed();
|
||||
auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
|
||||
|
||||
QString key{""};
|
||||
QString localeStr{defaultKeyStr};
|
||||
// NOTE:
|
||||
// We are process "localized keys" here, for usage check:
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
|
||||
qsizetype localeBegin = keyStr.indexOf('[');
|
||||
qsizetype localeEnd = keyStr.lastIndexOf(']');
|
||||
if ((localeBegin == -1 && localeEnd != -1) || (localeBegin != -1 && localeEnd == -1)) {
|
||||
qWarning() << "unmatched [] detected in desktop file, skip this line: " << line;
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
if (localeBegin == -1 && localeEnd == -1) {
|
||||
key = keyStr;
|
||||
} else {
|
||||
key = keyStr.sliced(0, localeBegin);
|
||||
localeStr = keyStr.sliced(localeBegin + 1, localeEnd - localeBegin - 1); // strip '[' and ']'
|
||||
}
|
||||
|
||||
static const QRegularExpression _re = []() {
|
||||
QRegularExpression tmp{"R([^A-Za-z0-9-])"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
// NOTE: https://stackoverflow.com/a/25583104
|
||||
thread_local const QRegularExpression re = _re;
|
||||
if (re.match(key).hasMatch()) {
|
||||
qWarning() << "invalid key name, skip this line:" << line;
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
if (localeStr != defaultKeyStr && !isInvalidLocaleString(localeStr)) {
|
||||
qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
|
||||
}
|
||||
|
||||
auto keyIt = group->find(key);
|
||||
if (keyIt != group->end() && keyIt->find(localeStr) != keyIt->end()) {
|
||||
qWarning() << "duplicated localestring, skip this line:" << line;
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
if (keyIt == group->end()) {
|
||||
group->insert(key, {{localeStr, valueStr}});
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
keyIt->insert(localeStr, valueStr);
|
||||
return ParserError::NoError;
|
||||
}
|
20
src/desktopfileparser.h
Normal file
20
src/desktopfileparser.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef DESKTOPFILEPARSER_H
|
||||
#define DESKTOPFILEPARSER_H
|
||||
|
||||
#include "iniParser.h"
|
||||
#include "desktopentry.h"
|
||||
|
||||
class DesktopFileParser : public Parser<DesktopEntry::Value>
|
||||
{
|
||||
public:
|
||||
using Parser<DesktopEntry::Value>::Parser;
|
||||
ParserError parse(Groups &ret) noexcept override;
|
||||
ParserError addGroup(Groups &ret) noexcept override;
|
||||
ParserError addEntry(Groups::iterator &group) noexcept override;
|
||||
};
|
||||
|
||||
#endif
|
38
src/global.h
38
src/global.h
@ -29,10 +29,12 @@ Q_DECLARE_LOGGING_CATEGORY(DDEAMProf)
|
||||
|
||||
using ObjectInterfaceMap = QMap<QString, QVariantMap>;
|
||||
using ObjectMap = QMap<QDBusObjectPath, ObjectInterfaceMap>;
|
||||
using PropMap = QMap<QString, QMap<QString, QString>>;
|
||||
using KVPairs = QMap<QString, QString>;
|
||||
using PropMap = QMap<QString, KVPairs>;
|
||||
|
||||
Q_DECLARE_METATYPE(ObjectInterfaceMap)
|
||||
Q_DECLARE_METATYPE(ObjectMap)
|
||||
Q_DECLARE_METATYPE(KVPairs)
|
||||
Q_DECLARE_METATYPE(PropMap)
|
||||
|
||||
struct SystemdUnitDBusMessage
|
||||
@ -401,23 +403,36 @@ inline QStringList getDesktopFileDirs()
|
||||
return XDGDataDirs;
|
||||
}
|
||||
|
||||
inline QStringList getAutoStartDirs()
|
||||
inline QString getXDGConfigHome()
|
||||
{
|
||||
auto XDGConfigHome = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME"));
|
||||
if (XDGConfigHome.isEmpty()) {
|
||||
XDGConfigHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".config";
|
||||
}
|
||||
|
||||
return XDGConfigHome;
|
||||
}
|
||||
|
||||
inline QStringList getXDGConfigDirs()
|
||||
{
|
||||
auto XDGConfigDirs = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_DIRS")).split(':', Qt::SkipEmptyParts);
|
||||
if (XDGConfigDirs.isEmpty()) {
|
||||
XDGConfigDirs.append("/etc/xdg");
|
||||
}
|
||||
|
||||
auto XDGConfigHome = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME"));
|
||||
if (XDGConfigHome.isEmpty()) {
|
||||
XDGConfigHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".config";
|
||||
}
|
||||
auto XDGConfigHome = getXDGConfigHome();
|
||||
|
||||
if (XDGConfigDirs.constFirst() != XDGConfigHome) {
|
||||
XDGConfigDirs.removeAll(XDGConfigHome);
|
||||
XDGConfigDirs.push_front(std::move(XDGConfigHome)); // guarantee XDG_CONFIG_HOME is first element.
|
||||
}
|
||||
|
||||
return XDGConfigDirs;
|
||||
}
|
||||
|
||||
inline QStringList getAutoStartDirs()
|
||||
{
|
||||
auto XDGConfigDirs = getXDGConfigDirs();
|
||||
std::for_each(XDGConfigDirs.begin(), XDGConfigDirs.end(), [](QString &str) {
|
||||
if (!str.endsWith(QDir::separator())) {
|
||||
str.append(QDir::separator());
|
||||
@ -428,6 +443,17 @@ inline QStringList getAutoStartDirs()
|
||||
return XDGConfigDirs;
|
||||
}
|
||||
|
||||
inline QString getCurrentDesktop()
|
||||
{
|
||||
auto desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")).split(';', Qt::SkipEmptyParts);
|
||||
|
||||
if (desktops.size() > 1) {
|
||||
qWarning() << "multi-DE is detected, use first value.";
|
||||
}
|
||||
|
||||
return desktops.first();
|
||||
}
|
||||
|
||||
inline bool isApplication(const QDBusObjectPath &path)
|
||||
{
|
||||
return path.path().split('/').last().startsWith("app");
|
||||
|
108
src/iniParser.h
Normal file
108
src/iniParser.h
Normal file
@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef INIPARSER_H
|
||||
#define INIPARSER_H
|
||||
|
||||
#include <QTextStream>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
|
||||
enum class ParserError {
|
||||
NoError,
|
||||
NotFound,
|
||||
MismatchedFile,
|
||||
InvalidLocation,
|
||||
InvalidFormat,
|
||||
OpenFailed,
|
||||
MissingInfo,
|
||||
Parsed,
|
||||
InternalError
|
||||
};
|
||||
|
||||
template <typename Value>
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
explicit Parser(QTextStream &stream)
|
||||
: m_stream(stream){};
|
||||
virtual ~Parser() = default;
|
||||
using Groups = QMap<QString, QMap<QString, Value>>;
|
||||
|
||||
Parser(const Parser &) = delete;
|
||||
Parser(Parser &&) = delete;
|
||||
Parser &operator=(const Parser &) = delete;
|
||||
Parser &operator=(Parser &&) = delete;
|
||||
|
||||
virtual ParserError parse(Groups &groups) noexcept = 0;
|
||||
virtual ParserError addGroup(Groups &groups) noexcept = 0;
|
||||
virtual ParserError addEntry(typename Groups::iterator &group) noexcept = 0;
|
||||
void skip() noexcept
|
||||
{
|
||||
while (!m_stream.atEnd() and (m_line.startsWith('#') or m_line.isEmpty())) {
|
||||
m_line = m_stream.readLine().trimmed();
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
QTextStream &m_stream;
|
||||
QString m_line;
|
||||
};
|
||||
|
||||
inline bool hasNonAsciiAndControlCharacters(const QString &str) noexcept
|
||||
{
|
||||
static const QRegularExpression _matchControlChars = []() {
|
||||
QRegularExpression tmp{R"(\p{Cc})"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto matchControlChars = _matchControlChars;
|
||||
static const QRegularExpression _matchNonAsciiChars = []() {
|
||||
QRegularExpression tmp{R"([^\x00-\x7f])"};
|
||||
tmp.optimize();
|
||||
return tmp;
|
||||
}();
|
||||
thread_local const auto matchNonAsciiChars = _matchNonAsciiChars;
|
||||
return str.contains(matchControlChars) and str.contains(matchNonAsciiChars);
|
||||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const ParserError &v)
|
||||
{
|
||||
QDebugStateSaver saver{debug};
|
||||
QString errMsg;
|
||||
switch (v) {
|
||||
case ParserError::NoError: {
|
||||
errMsg = "no error.";
|
||||
} break;
|
||||
case ParserError::NotFound: {
|
||||
errMsg = "file not found.";
|
||||
} break;
|
||||
case ParserError::MismatchedFile: {
|
||||
errMsg = "file type is mismatched.";
|
||||
} break;
|
||||
case ParserError::InvalidLocation: {
|
||||
errMsg = "file location is invalid, please check $XDG_DATA_DIRS.";
|
||||
} break;
|
||||
case ParserError::OpenFailed: {
|
||||
errMsg = "couldn't open the file.";
|
||||
} break;
|
||||
case ParserError::InvalidFormat: {
|
||||
errMsg = "the format of desktopEntry file is invalid.";
|
||||
} break;
|
||||
case ParserError::MissingInfo: {
|
||||
errMsg = "missing required infomation.";
|
||||
} break;
|
||||
case ParserError::Parsed: {
|
||||
errMsg = "this desktop entry is parsed.";
|
||||
} break;
|
||||
case ParserError::InternalError: {
|
||||
errMsg = "internal error of parser.";
|
||||
} break;
|
||||
}
|
||||
debug << errMsg;
|
||||
return debug;
|
||||
}
|
||||
|
||||
#endif
|
99
src/mimefileparser.cpp
Normal file
99
src/mimefileparser.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "mimefileparser.h"
|
||||
#include <QDebug>
|
||||
|
||||
ParserError MimeFileParser::parse(Groups &ret) noexcept
|
||||
{
|
||||
std::remove_reference_t<decltype(ret)> groups;
|
||||
while (!m_stream.atEnd()) {
|
||||
auto err = addGroup(groups);
|
||||
if (err != ParserError::NoError) {
|
||||
ret.clear();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_line.isEmpty()) {
|
||||
qCritical() << "Something is wrong in mimeapp.list parser, check logic.";
|
||||
ret.clear();
|
||||
return ParserError::InternalError;
|
||||
}
|
||||
|
||||
ret = std::move(groups);
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
ParserError MimeFileParser::addGroup(Groups &ret) noexcept
|
||||
{
|
||||
skip();
|
||||
if (!m_line.startsWith('[')) {
|
||||
qWarning() << "Invalid mimeapp.list format: unexpected line:" << m_line;
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
// Parsing group header, this format is same as desktop file's group
|
||||
|
||||
auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
|
||||
|
||||
if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
|
||||
qWarning() << "group header invalid:" << m_line;
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
if (m_desktopSpec and (groupHeader == addedAssociations or groupHeader == removedAssociations)) {
|
||||
qWarning()
|
||||
<< "desktop-specific mimeapp.list is not possible to add or remove associations from these files, skip this group.";
|
||||
while (!m_stream.atEnd() && !m_line.startsWith('[')) {
|
||||
skip();
|
||||
if (m_line.startsWith('[')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
Groups::iterator group;
|
||||
if (group = ret.find(groupHeader); group == ret.end()) {
|
||||
group = ret.insert(groupHeader, {});
|
||||
}
|
||||
|
||||
m_line.clear();
|
||||
while (!m_stream.atEnd() && !m_line.startsWith('[')) {
|
||||
skip();
|
||||
if (m_line.startsWith('[')) {
|
||||
break;
|
||||
}
|
||||
auto err = addEntry(group);
|
||||
if (err != ParserError::NoError) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return ParserError::NoError;
|
||||
}
|
||||
|
||||
ParserError MimeFileParser::addEntry(Groups::iterator &group) noexcept
|
||||
{
|
||||
auto line = m_line;
|
||||
m_line.clear();
|
||||
auto splitCharIndex = line.indexOf('=');
|
||||
if (splitCharIndex == -1) {
|
||||
qWarning() << "invalid line in desktop file, skip it:" << line;
|
||||
return ParserError::NoError;
|
||||
}
|
||||
auto keyStr = line.first(splitCharIndex).trimmed();
|
||||
auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
|
||||
|
||||
if (valueStr.isEmpty()) {
|
||||
return ParserError::InvalidFormat;
|
||||
}
|
||||
|
||||
auto newValues = valueStr.split(';', Qt::SkipEmptyParts);
|
||||
auto value = group->value(keyStr);
|
||||
value.append(newValues);
|
||||
group->insert(keyStr, newValues);
|
||||
|
||||
return ParserError::NoError;
|
||||
}
|
33
src/mimefileparser.h
Normal file
33
src/mimefileparser.h
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef MIMEAPPFILEPARSER_H
|
||||
#define MIMEAPPFILEPARSER_H
|
||||
|
||||
#include "iniParser.h"
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
|
||||
constexpr auto defaultApplications = "Default Applications";
|
||||
constexpr auto addedAssociations = "Added Associations";
|
||||
constexpr auto removedAssociations = "Removed Associations";
|
||||
constexpr auto mimeCache = "MIME Cache";
|
||||
|
||||
class MimeFileParser : public Parser<QStringList>
|
||||
{
|
||||
public:
|
||||
explicit MimeFileParser(QTextStream &stream, bool isDesktopSpecific)
|
||||
: Parser(stream)
|
||||
, m_desktopSpec(isDesktopSpecific)
|
||||
{
|
||||
}
|
||||
ParserError parse(Groups &ret) noexcept override;
|
||||
ParserError addGroup(Groups &ret) noexcept override;
|
||||
ParserError addEntry(Groups::iterator &group) noexcept override;
|
||||
|
||||
private:
|
||||
bool m_desktopSpec;
|
||||
};
|
||||
|
||||
#endif
|
@ -19,7 +19,7 @@ public:
|
||||
auto curDir = QDir::current();
|
||||
QByteArray fakeXDG = (curDir.absolutePath() + QDir::separator() + "data").toLocal8Bit();
|
||||
ASSERT_TRUE(qputenv("XDG_DATA_DIRS", fakeXDG));
|
||||
DesktopErrorCode err;
|
||||
ParserError err;
|
||||
auto file = DesktopFile::searchDesktopFileById("deepin-editor", err);
|
||||
if (!file.has_value()) {
|
||||
qWarning() << "search failed:" << err;
|
||||
@ -58,7 +58,7 @@ TEST_F(TestDesktopEntry, prase)
|
||||
ASSERT_TRUE(in.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text));
|
||||
QTextStream fs{&in};
|
||||
auto err = entry.parse(fs);
|
||||
ASSERT_EQ(err, DesktopErrorCode::NoError);
|
||||
ASSERT_EQ(err, ParserError::NoError);
|
||||
|
||||
auto group = entry.group("Desktop Entry");
|
||||
ASSERT_TRUE(group);
|
||||
|
Loading…
x
Reference in New Issue
Block a user