From dcad9e3af82a70911302ada183c7b4411a1e6562 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Thu, 17 Sep 2026 16:58:09 +0800 Subject: [PATCH] feat: add config catalog query and client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add ConfigInfo struct and D-Bus metatype registration for (appId, resourceId, subpath) triples 2. Add DSGConfigCatalog on the daemon side to enumerate local configuration files across generic meta directories and per-app directories 3. Add ResourceCatalogClient to query the daemon over the system bus and fall back to local directory scanning when the daemon is unreachable 4. Add the Internal D-Bus interface XML exposing configurations() method and configurationsChanged signal 5. Provide helper methods on the client for querying applications, resources per app, all-app resources, subpaths, and existence checks Log: Added configuration catalog querying with D-Bus interface and local fallback Influence: 1. Verify ConfigInfo serialization and deserialization over D-Bus 2. Test DSGConfigCatalog enumeration with multiple app directories and nested subpaths 3. Verify configurationsChanged signal triggers client cache refresh 4. Test ResourceCatalogClient fallback to local directories when the daemon is unavailable 5. Validate resource filtering for NoAppId and per-app queries 6. Test subpath enumeration and exclusion of the root configuration 7. Verify availableResourcesForApp merges per-app and all-app resources without duplicates feat: 添加配置目录查询与客户端 1. 新增 ConfigInfo 结构体及 D-Bus 元类型注册,用于 (appId, resourceId, subpath) 三元组 2. 在守护进程侧新增 DSGConfigCatalog,用于遍历通用元目录及各应用目录下的 本地配置文件 3. 新增 ResourceCatalogClient,通过系统总线查询守护进程,在守护进程不可 用时回退到本地目录扫描 4. 新增 Internal D-Bus 接口 XML,暴露 configurations() 方法及 configurationsChanged 信号 5. 客户端提供查询应用列表、应用资源、全局资源、子路径及存在性检查的辅助 方法 Log: 新增配置目录查询功能,含 D-Bus 接口与本地回退 Influence: 1. 验证 ConfigInfo 在 D-Bus 上的序列化与反序列化 2. 测试 DSGConfigCatalog 对多应用目录及嵌套子路径的遍历 3. 验证 configurationsChanged 信号能触发客户端缓存刷新 4. 测试守护进程不可用时 ResourceCatalogClient 回退到本地目录的逻辑 5. 验证 NoAppId 与各应用查询的资源过滤 6. 测试子路径遍历并确认根配置被排除 7. 验证 availableResourcesForApp 合并各应用与全局资源且无重复 --- dconfig-center/common/configinfo.h | 60 +++++++ .../common/resourcecatalogclient.cpp | 152 ++++++++++++++++++ dconfig-center/common/resourcecatalogclient.h | 38 +++++ .../dde-dconfig-daemon/dconfigcatalog.cpp | 106 ++++++++++++ .../dde-dconfig-daemon/dconfigcatalog.h | 28 ++++ ...org.desktopspec.ConfigManager.Internal.xml | 11 ++ 6 files changed, 395 insertions(+) create mode 100644 dconfig-center/common/configinfo.h create mode 100644 dconfig-center/common/resourcecatalogclient.cpp create mode 100644 dconfig-center/common/resourcecatalogclient.h create mode 100644 dconfig-center/dde-dconfig-daemon/dconfigcatalog.cpp create mode 100644 dconfig-center/dde-dconfig-daemon/dconfigcatalog.h create mode 100644 dconfig-center/dde-dconfig-daemon/services/org.desktopspec.ConfigManager.Internal.xml diff --git a/dconfig-center/common/configinfo.h b/dconfig-center/common/configinfo.h new file mode 100644 index 0000000..f7699f7 --- /dev/null +++ b/dconfig-center/common/configinfo.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +class ConfigInfo +{ +public: + static void registerMetaType(); + + QString appId; + QString resourceId; + QString subpath; + + bool operator==(const ConfigInfo &other) const + { + return appId == other.appId && resourceId == other.resourceId && subpath == other.subpath; + } +}; + +using ConfigInfoList = QList; + +Q_DECLARE_METATYPE(ConfigInfo) +Q_DECLARE_METATYPE(ConfigInfoList) + +inline void ConfigInfo::registerMetaType() +{ + static bool registered = false; + if (registered) + return; + + registered = true; + qRegisterMetaType("ConfigInfo"); + qDBusRegisterMetaType(); + qRegisterMetaType("ConfigInfoList"); + qDBusRegisterMetaType(); +} + +inline QDBusArgument &operator<<(QDBusArgument &argument, const ConfigInfo &info) +{ + argument.beginStructure(); + argument << info.appId << info.resourceId << info.subpath; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, ConfigInfo &info) +{ + argument.beginStructure(); + argument >> info.appId >> info.resourceId >> info.subpath; + argument.endStructure(); + return argument; +} diff --git a/dconfig-center/common/resourcecatalogclient.cpp b/dconfig-center/common/resourcecatalogclient.cpp new file mode 100644 index 0000000..ad8795d --- /dev/null +++ b/dconfig-center/common/resourcecatalogclient.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "resourcecatalogclient.h" +#include "helper.hpp" + +#include "configmanager_internal_interface.h" + +#include +#include +#include +#include + +namespace { +const char kServiceName[] = "org.desktopspec.ConfigManager"; +const char kObjectPath[] = "/Internal"; +} + +ResourceCatalogClient &ResourceCatalogClient::instance() +{ + // Keep the QObject alive until process exit. A static value would be + // destroyed after QCoreApplication and may access an invalid DBus connection. + static ResourceCatalogClient *client = new ResourceCatalogClient(); + return *client; +} + +ResourceCatalogClient::ResourceCatalogClient(QObject *parent) + : QObject(parent) + , m_interface(new DSGConfigInternal(kServiceName, kObjectPath, + QDBusConnection::systemBus(), this)) +{ + ConfigInfo::registerMetaType(); + + connect(m_interface, &DSGConfigInternal::configurationsChanged, + this, &ResourceCatalogClient::refresh); +} + +ConfigInfoList ResourceCatalogClient::localConfigurations() const +{ + ConfigInfoList result; + for (const QString &app : ::applications()) { + const QStringList resources = ::resourcesForApp(app); + for (const QString &resource : resources) { + result.append({app, resource, QString()}); + const QStringList subpaths = ::subpathsForResource(app, resource); + for (const QString &subpath : subpaths) + result.append({app, resource, subpath}); + } + } + return result; +} + +void ResourceCatalogClient::refresh() +{ + m_configurations.clear(); + m_configurationsValid = false; +} + +QStringList ResourceCatalogClient::applications() +{ + QStringList result; + result << NoAppId; + + QSet uniqueApps; + const auto configs = configurations(); + for (const auto &config : configs) { + if (!config.appId.isEmpty()) + uniqueApps.insert(config.appId); + } + result.append(uniqueApps.values()); + return result; +} + +ConfigInfoList ResourceCatalogClient::configurations() +{ + if (m_configurationsValid) + return m_configurations; + + auto reply = m_interface->configurations(); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Failed to query configurations from dde-dconfig-daemon:" + << reply.error().message() << ", fallback to local directories."; + return localConfigurations(); + } + + m_configurations = reply.value(); + m_configurationsValid = true; + return m_configurations; +} + +QStringList ResourceCatalogClient::resourcesForApp(const QString &appid) +{ + QStringList result; + const auto configs = configurations(); + for (const auto &config : configs) { + if (config.appId == appid && !result.contains(config.resourceId)) + result.append(config.resourceId); + } + return result; +} + +QStringList ResourceCatalogClient::resourcesForAllApp() +{ + QStringList result; + const auto configs = configurations(); + for (const auto &config : configs) { + if (config.appId.isEmpty() && !result.contains(config.resourceId)) + result.append(config.resourceId); + } + return result; +} + +QStringList ResourceCatalogClient::subpathsForResource(const QString &appid, const QString &resourceId) +{ + QStringList result; + const auto configs = configurations(); + for (const auto &config : configs) { + // The root configuration is displayed as the resource item itself. + // Only non-empty subpath directories are displayed as child items. + if (config.appId == appid && config.resourceId == resourceId + && !config.subpath.isEmpty() && !result.contains(config.subpath)) { + result.append(config.subpath); + } + } + return result; +} + +QStringList ResourceCatalogClient::availableResourcesForApp(const QString &appid) +{ + QStringList result; + if (!appid.isEmpty()) + result.append(resourcesForApp(appid)); + result.append(resourcesForAllApp()); + + QSet unique; + for (const QString &resource : result) + unique.insert(resource); + return unique.values(); +} + +bool ResourceCatalogClient::existAppid(const QString &appid) +{ + return !resourcesForApp(appid).isEmpty(); +} + +bool ResourceCatalogClient::existResource(const QString &appid, const QString &resourceId) +{ + return resourcesForApp(appid).contains(resourceId) + || resourcesForAllApp().contains(resourceId); +} diff --git a/dconfig-center/common/resourcecatalogclient.h b/dconfig-center/common/resourcecatalogclient.h new file mode 100644 index 0000000..6126b07 --- /dev/null +++ b/dconfig-center/common/resourcecatalogclient.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "configinfo.h" + +#include +#include + +class DSGConfigInternal; + +class ResourceCatalogClient : public QObject +{ + Q_OBJECT +public: + static ResourceCatalogClient &instance(); + + QStringList applications(); + ConfigInfoList configurations(); + void refresh(); + QStringList resourcesForApp(const QString &appid); + QStringList resourcesForAllApp(); + QStringList subpathsForResource(const QString &appid, const QString &resourceId); + + QStringList availableResourcesForApp(const QString &appid); + bool existAppid(const QString &appid); + bool existResource(const QString &appid, const QString &resourceId); + +private: + explicit ResourceCatalogClient(QObject *parent = nullptr); + ConfigInfoList localConfigurations() const; + + DSGConfigInternal *m_interface = nullptr; + ConfigInfoList m_configurations; + bool m_configurationsValid = false; +}; diff --git a/dconfig-center/dde-dconfig-daemon/dconfigcatalog.cpp b/dconfig-center/dde-dconfig-daemon/dconfigcatalog.cpp new file mode 100644 index 0000000..07cded2 --- /dev/null +++ b/dconfig-center/dde-dconfig-daemon/dconfigcatalog.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dconfigcatalog.h" + +#include +#include +#include +#include + +namespace { +QString subpathOf(const QString &baseDir, const QString &dir) +{ + const QString relativePath = QDir(baseDir).relativeFilePath(dir); + return relativePath == QLatin1String(".") || relativePath.isEmpty() + ? QString() : QLatin1Char('/') + relativePath; +} + +QStringList resourcesForSubpath(const QString &baseDir, const QString &subpath) +{ + QStringList result; + QDir dir(baseDir); + if (!subpath.isEmpty()) + dir.cd(subpath.mid(1)); + + do { + const auto entries = dir.entryInfoList({QLatin1String("*.json")}, + QDir::Files | QDir::Readable); + for (const QFileInfo &entry : entries) { + const QString resource = entry.fileName().chopped(QStringLiteral(".json").size()); + if (!result.contains(resource)) + result.append(resource); + } + } while (dir != QDir(baseDir) && dir.cdUp()); + + return result; +} + +void appendConfigurations(ConfigInfoList &result, QSet &uniqueKeys, + const QString &appId, const QString &baseDir, + const QString &localPrefix) +{ + QStringList dirs; + dirs.append(baseDir); + + if (appId != NoAppId) { + QDirIterator dirIterator(baseDir, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable, + QDirIterator::Subdirectories); + while (dirIterator.hasNext()) + dirs.append(dirIterator.next()); + } + + for (const QString &dir : dirs) { + const QString subpath = subpathOf(baseDir, dir); + const QStringList resources = resourcesForSubpath(baseDir, subpath); + for (const QString &resource : resources) { + Dtk::Core::DConfigFile file(appId, resource, subpath); + if (!file.load(localPrefix)) + continue; + + const QString key = QStringLiteral("%1/%2/%3").arg(appId, resource, subpath); + if (!uniqueKeys.contains(key)) { + uniqueKeys.insert(key); + result.append({appId, resource, subpath}); + } + } + } +} +} + +DSGConfigCatalog::DSGConfigCatalog(QObject *parent) + : QObject(parent) +{ + ConfigInfo::registerMetaType(); +} + +void DSGConfigCatalog::setLocalPrefix(const QString &localPrefix) +{ + if (m_localPrefix == localPrefix) + return; + + m_localPrefix = localPrefix; + Q_EMIT configurationsChanged(); +} + +ConfigInfoList DSGConfigCatalog::configurations() const +{ + ConfigInfoList result; + QSet uniqueKeys; + + for (const QString &dir : Dtk::Core::DConfigMeta::genericMetaDirs(m_localPrefix)) { + appendConfigurations(result, uniqueKeys, NoAppId, dir, m_localPrefix); + + const auto appDirs = QDir(dir).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable); + for (const QString &appDir : appDirs) { + if (appDir == QLatin1String("overrides")) + continue; + + appendConfigurations(result, uniqueKeys, appDir, QDir(dir).filePath(appDir), + m_localPrefix); + } + } + + return result; +} diff --git a/dconfig-center/dde-dconfig-daemon/dconfigcatalog.h b/dconfig-center/dde-dconfig-daemon/dconfigcatalog.h new file mode 100644 index 0000000..db9d5f2 --- /dev/null +++ b/dconfig-center/dde-dconfig-daemon/dconfigcatalog.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "configinfo.h" +#include "dconfig_global.h" + +#include + +class DSGConfigCatalog : public QObject +{ + Q_OBJECT +public: + explicit DSGConfigCatalog(QObject *parent = nullptr); + + void setLocalPrefix(const QString &localPrefix); + +public Q_SLOTS: + ConfigInfoList configurations() const; + +Q_SIGNALS: + void configurationsChanged(); + +private: + QString m_localPrefix; +}; diff --git a/dconfig-center/dde-dconfig-daemon/services/org.desktopspec.ConfigManager.Internal.xml b/dconfig-center/dde-dconfig-daemon/services/org.desktopspec.ConfigManager.Internal.xml new file mode 100644 index 0000000..b51ed42 --- /dev/null +++ b/dconfig-center/dde-dconfig-daemon/services/org.desktopspec.ConfigManager.Internal.xml @@ -0,0 +1,11 @@ + + + + + + + + + +