Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions dconfig-center/common/configinfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QDBusArgument>
#include <QDBusMetaType>
#include <QList>
#include <QMetaType>
#include <QString>

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<ConfigInfo>;

Q_DECLARE_METATYPE(ConfigInfo)
Q_DECLARE_METATYPE(ConfigInfoList)

inline void ConfigInfo::registerMetaType()
{
static bool registered = false;
if (registered)
return;

registered = true;
qRegisterMetaType<ConfigInfo>("ConfigInfo");
qDBusRegisterMetaType<ConfigInfo>();
qRegisterMetaType<ConfigInfoList>("ConfigInfoList");
qDBusRegisterMetaType<ConfigInfoList>();
}

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;
}
152 changes: 152 additions & 0 deletions dconfig-center/common/resourcecatalogclient.cpp
Original file line number Diff line number Diff line change
@@ -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 <QDBusConnection>
#include <QDBusPendingReply>
#include <QLoggingCategory>
#include <QSet>

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});
Comment on lines +43 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): The local fallback and daemon catalog disagree on generic directories containing nested subdirectories: the fallback's applications() helper treats each nested generic directory as an application and reports its files with that directory name as appId, while appendConfigurations deliberately does not recurse for NoAppId. The same generic nested configuration therefore receives a different identity, or disappears entirely, when the daemon is unavailable.

Triggers: When a generic configuration directory contains nested subpath directories and the daemon is unavailable.

Suggested fix: Use the same generic-directory traversal and NoAppId/subpath interpretation in the fallback as in DSGConfigCatalog, rather than deriving applications from generic directory children.

}
}
return result;
}

void ResourceCatalogClient::refresh()
{
m_configurations.clear();
m_configurationsValid = false;
}

QStringList ResourceCatalogClient::applications()
{
QStringList result;
result << NoAppId;

QSet<QString> 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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (performance): A failed D-Bus query returns localConfigurations() without setting m_configurations or m_configurationsValid, so every subsequent helper call synchronously retries the unavailable system-bus request and rescans all local directories. Calls such as applications(), resourcesForApp(), and availableResourcesForApp() consequently repeat the fallback work instead of using the catalog cache.

Triggers: When the daemon is unreachable and a caller performs multiple catalog queries.

Suggested fix: Cache the fallback result and mark it valid, while invalidating it when a later daemon/signal event indicates that a refresh is needed.

Suggested change
return localConfigurations();
m_configurations = localConfigurations();
m_configurationsValid = true;
return m_configurations;

}

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<QString> 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);
}
38 changes: 38 additions & 0 deletions dconfig-center/common/resourcecatalogclient.h
Original file line number Diff line number Diff line change
@@ -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 <QObject>
#include <QStringList>

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;
};
106 changes: 106 additions & 0 deletions dconfig-center/dde-dconfig-daemon/dconfigcatalog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2026 Uniontech Software Technology Co.,Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "dconfigcatalog.h"

#include <DConfigFile>
#include <QDirIterator>
#include <QFileInfo>
#include <QSet>

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<QString> &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<QString> 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;
}
28 changes: 28 additions & 0 deletions dconfig-center/dde-dconfig-daemon/dconfigcatalog.h
Original file line number Diff line number Diff line change
@@ -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 <QObject>

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;
};
Loading
Loading