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 @@ + + + + + + + + + +