From 15e1a527c4a8bfb4a12ddda36b28cccb9cbbac69 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Mon, 17 Aug 2026 11:39:15 +0800 Subject: [PATCH 1/3] fix: add dev-tools package dependencies to DTK dev packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add libdtk6core-bin dependency to libdtk6core-dev package (DTK6) 2. Add libdtkcore5-bin dependency to libdtkcore-dev package (DTK5) 3. Ensure development packages pull in matching version tools packages 4. Maintain consistent version alignment between dev libraries and tools Log: Enhanced DTK development packages to include matching tools dependencies Influence: 1. Verify dev package installation includes corresponding tools packages 2. Confirm version alignment between dev and tools packages 3. Test installation with versioned dependencies in offline environments 4. Validate compatibility with existing build systems using DTK dev packages chore: 为 DTK 开发包添加配套工具包依赖 1. 为 libdtk6core-dev 包(DTK6)添加 libdtk6core-bin 依赖 2. 为 libdtkcore-dev 包(DTK5)添加 libdtkcore5-bin 依赖 3. 确保开发包安装时自动引入对应版本的工具包 4. 保持开发库与工具包之间版本一致性 Log: 增强 DTK 开发包,新增配套工具包依赖 Influence: 1. 验证开发包安装时是否包含对应的工具包 2. 确认开发包与工具包之间版本一致性 3. 在离线环境中测试带版本依赖的安装过程 4. 验证现有使用 DTK 开发包的构建系统兼容性 --- debian/control | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/control b/debian/control index b987b7c4..fd0e1479 100644 --- a/debian/control +++ b/debian/control @@ -46,6 +46,7 @@ Description: Development ToolKit Core Utilities (DTK6 with Qt6) Package: libdtk6core-dev Architecture: any Depends: libdtk6core( =${binary:Version}), + libdtk6core-bin( =${binary:Version}), libdtkcommon-dev(>=5.6.16), libdtk6log-dev Build-Profiles: Description: Development ToolKit Core Devel Library (DTK6 with Qt6) @@ -84,6 +85,7 @@ Description: Development ToolKit Core Utilities (DTK5 with Qt5) Package: libdtkcore-dev Architecture: any Depends: libdtkcore5( =${binary:Version}), + libdtkcore5-bin( =${binary:Version}), libdtkcommon-dev(>=5.6.16), libdtklog-dev Build-Profiles: Description: Development ToolKit Core Devel Library (DTK5 with Qt5) From 148687056070741eae5f1c3034124663a5fa0846 Mon Sep 17 00:00:00 2001 From: wubowen Date: Wed, 19 Jul 2023 13:43:06 +0800 Subject: [PATCH 2/3] chore: add Permissions function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加管控类型方法调用的返回值 Log: 添加管控类型 Influence: 管控进程. Task: https://pms.uniontech.com/task-view-260335.html Task: https://pms.uniontech.com/task-view-260333.html Change-Id: If91612d7b3e824b26081f169d5acc108cd1cef98 (cherry picked from commit b1e3ac4c547682544474102e9acd078e026e23f4) --- include/global/dconfigfile.h | 4 +++- src/dconfigfile.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/global/dconfigfile.h b/include/global/dconfigfile.h index 5959fd9f..37543ef4 100644 --- a/include/global/dconfigfile.h +++ b/include/global/dconfigfile.h @@ -35,7 +35,9 @@ class LIBDTKCORESHARED_EXPORT DConfigFile : public DObject{ enum Permissions { ReadOnly, - ReadWrite + ReadWrite, + AuthorizedReadOnly, + AuthorizedReadWrite, }; enum Visibility { diff --git a/src/dconfigfile.cpp b/src/dconfigfile.cpp index af1d5884..60886350 100644 --- a/src/dconfigfile.cpp +++ b/src/dconfigfile.cpp @@ -386,8 +386,13 @@ class Q_DECL_HIDDEN DConfigInfo { { DConfigFile::Permissions p = DConfigFile::ReadOnly; const auto &tmp = values[key][QLatin1String("permissions")].toString(); - if (tmp == QLatin1String("readwrite")) + if (tmp == QLatin1String("readwrite")) { p = DConfigFile::ReadWrite; + } else if (tmp == QLatin1String("authorizedreadonly")) { + p = DConfigFile::AuthorizedReadOnly; + } else if (tmp == QLatin1String("authorizedreadwrite")) { + p = DConfigFile::AuthorizedReadWrite; + } return p; } From b1800e76b73acf3b365facd6db4c58881b57034c Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Wed, 2 Sep 2026 17:21:03 +0800 Subject: [PATCH 3/3] fix: overlay prefixed cache on legacy cache on load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Refactor DConfigCacheImpl::load to support loading cache from both legacy path and newly configured path prefix 2. Implement loadCache helper returning detailed status (NotFound/ Loaded/Invalid) and tracking loaded keys 3. When cache prefix is set, load legacy cache first then merge with prefixed cache so legacy-only values are not lost 4. Track cache changes when legacy keys are missing from new overlay cache 5. Add comprehensive tests covering user cache overlay, global cache overlay, and no-appid/subpath scenarios 6. Preserve save behavior which always persists merged state to the prefix path 7. Update documentation for setCachePathPrefix explaining the new merge behavior Log: Cache path prefix now preserves legacy configuration values during migration Influence: 1. Verify setCachePathPrefix with no prior cache creates a new prefixed cache correctly 2. Test migration scenario where legacy cache exists with different values than new prefixed cache 3. Check global config applies prefix overlay behavior correctly 4. Validate configs without appid and with subpath work with cache overlay 5. Confirm saving after merged load persists legacy-only values into the new prefix 6. Ensure files without cache prefix still load exactly as before 7. Test invalid cache files in legacy path do not block new cache loading fix: 在加载时将前缀缓存叠加到传统缓存之上 1. 重构 DConfigCacheImpl::load,支持同时从传统路径和新设置的路径前缀加载 缓存 2. 实现 loadCache 辅助函数,返回详细状态(NotFound/Loaded/Invalid)并跟 踪已加载的键 3. 设置缓存前缀时,先加载传统缓存,再合并前缀缓存,避免丢失仅存在于传统 缓存中的值 4. 当新覆盖缓存缺少传统键时,跟踪缓存变化状态 5. 添加全面测试,覆盖用户缓存覆盖、全局缓存覆盖、无应用ID和子路径场景 6. 保持保存行为不变,始终将合并后的状态写入前缀路径 7. 更新 setCachePathPrefix 文档,说明新的合并行为 Log: 缓存路径前缀在迁移过程中现在可以保留传统配置值 Influence: 1. 验证使用 setCachePathPrefix 且无历史缓存时能正确创建新前缀缓存 2. 测试传统缓存与新前缀缓存存在不同值时的迁移场景 3. 检查全局配置是否正确应用前缀覆盖行为 4. 验证无应用ID和带子路径的配置能正常工作于缓存覆盖 5. 确认合并加载后的保存操作能够将传统独有值持久化到新前缀 6. 确保未设置缓存前缀时的文件加载行为完全不变 7. 测试传统路径中存在无效缓存文件时不会阻止新缓存加载 --- include/global/dconfigfile.h | 2 +- src/dconfigfile.cpp | 94 +++++++++++++++----- tests/ut_dconfigfile.cpp | 167 +++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 21 deletions(-) diff --git a/include/global/dconfigfile.h b/include/global/dconfigfile.h index 37543ef4..6865314f 100644 --- a/include/global/dconfigfile.h +++ b/include/global/dconfigfile.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2021 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/src/dconfigfile.cpp b/src/dconfigfile.cpp index 60886350..d5a9512c 100644 --- a/src/dconfigfile.cpp +++ b/src/dconfigfile.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -1085,7 +1086,9 @@ DConfigMetaImpl::~DConfigMetaImpl() @~english @fn void setCachePathPrefix(const QString &prefix) = 0; @brief Set cache's prefix path, it's access permissions is considered by caller, -and it needs to distinguish the paths of different caches by caller. +and it needs to distinguish the paths of different caches by caller. When a +prefix is set, load the legacy cache first and then overlay the cache stored +under the prefix. Saving always uses the prefix path. @param prefix cache's prefix path. */ @@ -1111,9 +1114,10 @@ class Q_DECL_HIDDEN DConfigCacheImpl : public DConfigCache { return values.keyList(); } - inline QString applicationCacheDir(const QString &localPrefix, const QString &suffix) const + inline QString applicationCacheDir(const QString &localPrefix, const QString &suffix, + const QString &pathPrefix) const { - QString prefix(cachePrefix); + QString prefix(pathPrefix); if (prefix.isEmpty()) { // If target user is current user or system user, then get the home path by environment variable first. QString homePath; @@ -1134,7 +1138,7 @@ class Q_DECL_HIDDEN DConfigCacheImpl : public DConfigCache { inline QString applicationCacheDir(const QString &localPrefix) const { - return applicationCacheDir(localPrefix, QString()); + return applicationCacheDir(localPrefix, QString(), cachePrefix); } inline QString cacheDir(const QString &basePath) { @@ -1142,8 +1146,9 @@ class Q_DECL_HIDDEN DConfigCacheImpl : public DConfigCache { return dir.filePath(configKey.fileName + FILE_SUFFIX); } - inline QString globalCacheDir(const QString &localPrefix) const { - QString prefix(cachePrefix); + inline QString globalCacheDir(const QString &localPrefix, const QString &pathPrefix) const + { + QString prefix(pathPrefix); if (prefix.isEmpty()) { // TODO `DSG_APP_DATA` is not set and `appid` is not captured in `DStandardPaths::path`. QString appDataDir = DStandardPaths::path(DStandardPaths::DSG::AppData); @@ -1166,20 +1171,33 @@ class Q_DECL_HIDDEN DConfigCacheImpl : public DConfigCache { return QDir::cleanPath(QString("%1/%2/%3").arg(localPrefix, prefix, configKey.appId)); } - QString getCacheDir(const QString &localPrefix = QString()) + QString getCacheDir(const QString &localPrefix, const QString &pathPrefix) const { if (isGlobal()) { - const QString &dir = globalCacheDir(localPrefix); + const QString &dir = globalCacheDir(localPrefix, pathPrefix); if (!dir.isEmpty()) return dir; // Not supported the global config, fallback the config cache data to user directory. - return applicationCacheDir(localPrefix, "-fake-global"); + return applicationCacheDir(localPrefix, "-fake-global", pathPrefix); } else { - return applicationCacheDir(localPrefix); + return applicationCacheDir(localPrefix, QString(), pathPrefix); } } + QString getCacheDir(const QString &localPrefix = QString()) const + { + return getCacheDir(localPrefix, cachePrefix); + } + + enum class CacheLoadStatus { + NotFound, + Loaded, + Invalid + }; + + CacheLoadStatus loadCache(const QString &dir, QSet *loadedKeys = nullptr); + bool load(const QString &localPrefix = QString()) override; bool isGlobal() const override @@ -1240,18 +1258,52 @@ DConfigCacheImpl::~DConfigCacheImpl() bool DConfigCacheImpl::load(const QString &localPrefix) { - // cache 文件要严格匹配 subpath - const QString &dir = getCacheDir(localPrefix); - if (dir.isEmpty()) { - return true; + const QString newCacheDir = getCacheDir(localPrefix); + if (cachePrefix.isEmpty()) + return loadCache(newCacheDir) != CacheLoadStatus::Invalid; + + const QString legacyCacheDir = getCacheDir(localPrefix, QString()); + if (legacyCacheDir == newCacheDir) + return loadCache(newCacheDir) != CacheLoadStatus::Invalid; + + QSet legacyKeys; + const auto legacyStatus = loadCache(legacyCacheDir, &legacyKeys); + if (legacyStatus == CacheLoadStatus::Invalid) { + qCWarning(cfLog, "Failed to load legacy cache from \"%s\"; continue with the new cache.", + qPrintable(legacyCacheDir)); + legacyKeys.clear(); } + + QSet newKeys; + const auto newStatus = loadCache(newCacheDir, &newKeys); + if (newStatus == CacheLoadStatus::Invalid) + return false; + + if (legacyStatus == CacheLoadStatus::Loaded) { + for (const QString &key : legacyKeys) { + if (!newKeys.contains(key)) { + cacheChanged = true; + break; + } + } + } + + return true; +} + +DConfigCacheImpl::CacheLoadStatus DConfigCacheImpl::loadCache(const QString &dir, + QSet *loadedKeys) +{ + // cache 文件要严格匹配 subpath + if (dir.isEmpty()) + return CacheLoadStatus::NotFound; + QScopedPointer cache(loadFile(dir, configKey.subpath, configKey.fileName + FILE_SUFFIX, false)); - if (!cache) { - return true; - } + if (!cache) + return CacheLoadStatus::NotFound; const JsonParseResult pr = loadJsonFile(cache.data()); const QJsonDocument &doc = pr.doc; @@ -1259,9 +1311,9 @@ bool DConfigCacheImpl::load(const QString &localPrefix) if (doc.isObject()) { const QJsonObject &root = doc.object(); if (!checkMagic(root, MAGIC_CACHE)) - return false; + return CacheLoadStatus::Invalid; if (!checkVersion(root, DConfigFile::supportedVersion())) - return false; + return CacheLoadStatus::Invalid; auto &&contents = root[QLatin1String("contents")].toObject(); auto i = contents.constBegin(); @@ -1278,9 +1330,11 @@ bool DConfigCacheImpl::load(const QString &localPrefix) jsonValueToVariant(valueField, isOriginalValueFloat(pr.raw, i.key(), valueField))); } values.update(i.key(), vh); + if (loadedKeys) + loadedKeys->insert(i.key()); } } - return true; + return CacheLoadStatus::Loaded; } bool DConfigCacheImpl::save(const QString &localPrefix, QJsonDocument::JsonFormat format, bool sync) diff --git a/tests/ut_dconfigfile.cpp b/tests/ut_dconfigfile.cpp index d00a231c..23227832 100644 --- a/tests/ut_dconfigfile.cpp +++ b/tests/ut_dconfigfile.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "test_helper.hpp" @@ -96,6 +97,35 @@ TEST_F(ut_DConfigFile, testLoad) { ASSERT_EQ(config.meta()->permissions("canExit"), DConfigFile::ReadWrite); } +TEST_F(ut_DConfigFile, authorizedPermissions) { + QByteArray meta = R"delimiter( +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "authorizedReadOnly": { + "value": true, + "serial": 0, + "permissions": "authorizedreadonly" + }, + "authorizedReadWrite": { + "value": true, + "serial": 0, + "permissions": "authorizedreadwrite" + } + } +} + )delimiter"; + + QBuffer buffer(&meta); + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(&buffer, {})); + ASSERT_EQ(config.meta()->permissions("authorizedReadOnly"), + DConfigFile::AuthorizedReadOnly); + ASSERT_EQ(config.meta()->permissions("authorizedReadWrite"), + DConfigFile::AuthorizedReadWrite); +} + TEST_F(ut_DConfigFile, setValueTypeCheck) { FileCopyGuard guard(":/data/dconf-example.meta.json", QString("%1/%2.json").arg(metaPath, FILE_NAME)); @@ -480,6 +510,143 @@ TEST_F(ut_DConfigFile, setCachePathPrefix) { } } +TEST_F(ut_DConfigFile, cachePathPrefixOverlaysNewCacheOnLegacyCache) { + FileCopyGuard guard(":/data/dconf-example.meta.json", + QString("%1/%2.json").arg(metaPath, FILE_NAME)); + const QString newPrefix("/configs-user-overlay"); + + // Create the new cache first so it does not inherit any legacy values. + { + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + cache->setCachePathPrefix(newPrefix); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_TRUE(config.setValue("key2", "new-value", "test", cache.get())); + ASSERT_TRUE(cache->save(LocalPrefix)); + } + + // The legacy cache contains one conflicting key and one legacy-only key. + { + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_TRUE(config.setValue("key2", "legacy-value", "test", cache.get())); + ASSERT_TRUE(config.setValue("readwrite", false, "test", cache.get())); + ASSERT_TRUE(cache->save(LocalPrefix)); + } + + { + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + cache->setCachePathPrefix(newPrefix); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_EQ(config.value("key2", cache.get()), QString("new-value")); + ASSERT_EQ(config.value("readwrite", cache.get()), false); + + // load() marks the merged cache dirty, so the existing save path + // persists the legacy-only value into the new cache. + ASSERT_TRUE(cache->save(LocalPrefix)); + } + + const QString legacyCache = QDir::cleanPath( + QString("%1/tmp/home/.config/dsg/configs/%2/%3.json") + .arg(LocalPrefix, APP_ID, FILE_NAME)); + ASSERT_TRUE(QFile::remove(legacyCache)); + + // The legacy-only value remains available from the saved new cache. + { + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + cache->setCachePathPrefix(newPrefix); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_EQ(config.value("key2", cache.get()), QString("new-value")); + ASSERT_EQ(config.value("readwrite", cache.get()), false); + } +} + +TEST_F(ut_DConfigFile, cachePathPrefixOverlaysGlobalCache) { + FileCopyGuard guard(":/data/dconf-example.meta.json", + QString("%1/%2.json").arg(metaPath, FILE_NAME)); + const QString newPrefix("/configs-global-overlay"); + + { + DConfigFile config(APP_ID, FILE_NAME); + config.globalCache()->setCachePathPrefix(newPrefix); + ASSERT_TRUE(config.load(LocalPrefix)); + ASSERT_TRUE(config.setValue("key3", "new-global", "test")); + ASSERT_TRUE(config.save(LocalPrefix)); + } + + { + DConfigFile config(APP_ID, FILE_NAME); + ASSERT_TRUE(config.load(LocalPrefix)); + ASSERT_TRUE(config.setValue("key3", "legacy-global", "test")); + ASSERT_TRUE(config.setValue("number", 42, "test")); + ASSERT_TRUE(config.save(LocalPrefix)); + } + + { + DConfigFile config(APP_ID, FILE_NAME); + config.globalCache()->setCachePathPrefix(newPrefix); + ASSERT_TRUE(config.load(LocalPrefix)); + ASSERT_EQ(config.value("key3"), QString("new-global")); + ASSERT_EQ(config.value("number"), 42); + ASSERT_TRUE(config.save(LocalPrefix)); + } + + const QString newCache = QDir::cleanPath( + QString("%1/%2/%3/%4.json").arg(LocalPrefix, newPrefix, APP_ID, FILE_NAME)); + QFile cacheFile(newCache); + ASSERT_TRUE(cacheFile.open(QIODevice::ReadOnly)); + const QJsonObject contents = QJsonDocument::fromJson(cacheFile.readAll()) + .object() + .value("contents") + .toObject(); + ASSERT_EQ(contents.value("key3").toObject().value("value").toString(), + QString("new-global")); + ASSERT_EQ(contents.value("number").toObject().value("value").toInt(), 42); +} + +TEST_F(ut_DConfigFile, cachePathPrefixSupportsNoAppIdAndSubpath) { + FileCopyGuard guard(":/data/dconf-example.meta.json", + QString("%1/%2.json").arg(noAppidMetaPath, FILE_NAME)); + const QString newPrefix("/configs-no-appid-overlay"); + const QString subpath("/nested/path"); + + { + DConfigFile config(NoAppId, FILE_NAME, subpath); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + cache->setCachePathPrefix(newPrefix); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_TRUE(config.setValue("readwrite", false, "test", cache.get())); + ASSERT_TRUE(cache->save(LocalPrefix)); + } + + { + DConfigFile config(NoAppId, FILE_NAME, subpath); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_TRUE(config.setValue("key2", "legacy-no-appid", "test", cache.get())); + ASSERT_TRUE(cache->save(LocalPrefix)); + } + + { + DConfigFile config(NoAppId, FILE_NAME, subpath); + ASSERT_TRUE(config.load(LocalPrefix)); + QScopedPointer cache(config.createUserCache(uid)); + cache->setCachePathPrefix(newPrefix); + ASSERT_TRUE(cache->load(LocalPrefix)); + ASSERT_EQ(config.value("readwrite", cache.get()), false); + ASSERT_EQ(config.value("key2", cache.get()), QString("legacy-no-appid")); + } +} + TEST_F(ut_DConfigFile, setSubpath) { FileCopyGuard guard(":/data/dconf-example.meta.json", QString("%1/%2.json").arg(metaPath, FILE_NAME));