From 8f18336584169d00a9c6357db4c58418143273b8 Mon Sep 17 00:00:00 2001 From: liyigang Date: Thu, 21 May 2026 10:29:28 +0800 Subject: [PATCH] refactor: extract DConfig loading and add index-level mutex for thread safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted all DConfig loading logic from searchutility.cpp into a dedicated SearchDConfig class with a SearchDConfigSnapshot struct. DConfig reads via DBus are not thread-safe, so all reads are now performed on the main thread and the resulting snapshot is passed to worker threads via custom options. Added IndexMutexGuard to provide per-index-directory mutual exclusion, preventing concurrent access to the same Lucene index directory since FSDirectory::open() returns cached instances sharing underlying buffers. These changes enable: 1. Thread-safe DConfig access by restricting reads to the main thread 2. Safe concurrent searching across multiple index directories 3. Cleaner separation of configuration loading concerns Influence: 1. Verify multi-threaded search scenarios no longer cause index corruption 2. Ensure DConfig snapshot is correctly propagated to all search strategies 3. Test path prefix query optimization with dynamic indexed directory changes refactor: 提取 DConfig 加载逻辑并添加索引级互斥锁以保障线程安全 Bug: https://pms.uniontech.com/bug-view-361809.html 将 searchutility.cpp 中所有 DConfig 加载逻辑提取到独立的 SearchDConfig 类, 使用 SearchDConfigSnapshot 结构体承载配置快照。DConfig 通过 DBus 读取, 非线程安全,因此所有读取操作限定在主线程执行,生成的快照通过自定义选项 传递给工作线程。 新增 IndexMutexGuard 提供按索引目录的互斥机制,防止多线程并发访问同一 Lucene 索引目录,因为 FSDirectory::open() 对相同路径返回缓存实例, 底层 IndexInput 缓冲区共享。 这些改动实现了: 1. 通过将 DConfig 读取限制在主线程来保障线程安全 2. 支持多索引目录间的安全并发搜索 3. 配置加载职责的清晰分离 Influence: 1. 验证多线程搜索场景不再导致索引损坏 2. 确保 DConfig 快照正确传递到所有搜索策略 3. 测试索引目录动态变化时路径前缀查询优化的正确性 --- .../contentstrategies/indexedstrategy.cpp | 9 +- .../core/genericsearchengine.cpp | 8 + .../core/searchstrategy/searchworker.cpp | 3 + .../filenamestrategies/indexedstrategy.cpp | 8 +- .../ocrtextstrategies/indexedstrategy.cpp | 8 +- .../dfm-search-lib/utils/indexmutex.h | 31 ++ .../dfm-search-lib/utils/searchdconfig.cpp | 223 ++++++++++++++ .../dfm-search-lib/utils/searchdconfig.h | 34 +++ .../dfm-search-lib/utils/searchutility.cpp | 282 ++---------------- .../dfm-search-lib/utils/searchutility.h | 8 + 10 files changed, 351 insertions(+), 263 deletions(-) create mode 100644 src/dfm-search/dfm-search-lib/utils/indexmutex.h create mode 100644 src/dfm-search/dfm-search-lib/utils/searchdconfig.cpp create mode 100644 src/dfm-search/dfm-search-lib/utils/searchdconfig.h diff --git a/src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp b/src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp index bc498aa6..fc611523 100644 --- a/src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp +++ b/src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp @@ -24,8 +24,10 @@ #include "utils/contenthighlighter.h" #include "utils/lucenequeryutils.h" #include "utils/searchutility.h" +#include "utils/searchdconfig.h" #include "utils/lucene_cancellation_compat.h" #include "utils/timerangeutils.h" +#include "utils/indexmutex.h" using namespace Lucene; @@ -101,8 +103,9 @@ Lucene::QueryPtr ContentIndexedStrategy::buildLuceneQuery(const SearchQuery &que } // Add path prefix query optimization + auto snapshot = m_options.customOption("dconfig_snapshot").value(); if (mainQuery && SearchUtility::isContentIndexAncestorPathsSupported() - && SearchUtility::shouldUsePathPrefixQuery(searchPath)) { + && SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) { QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath, QString::fromWCharArray(LuceneFieldNames::Content::kAncestorPaths)); if (pathPrefixQuery) { @@ -417,6 +420,10 @@ void ContentIndexedStrategy::performContentSearch(const SearchQuery &query) SearchCancellationGuard guard(&m_cancelled); try { + // 加索引级互斥锁,防止多线程并发访问同一 Lucene 索引目录 + // FSDirectory::open() 对相同路径返回缓存实例,底层 IndexInput 缓冲区共享 + QMutexLocker indexLock(&IndexMutexGuard::mutexForPath(m_indexDir)); + // 获取索引目录 FSDirectoryPtr directory = FSDirectory::open(m_indexDir.toStdWString()); if (!directory) { diff --git a/src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp b/src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp index 667f5176..85c50766 100644 --- a/src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp +++ b/src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp @@ -8,6 +8,8 @@ #include #include +#include "utils/searchdconfig.h" + DFM_SEARCH_BEGIN_NS DCORE_USE_NAMESPACE @@ -116,6 +118,9 @@ void GenericSearchEngine::search(const SearchQuery &query) return; } + // 主线程实时读取 DConfig 配置,通过参数传入工作线程 + m_options.setCustomOption("dconfig_snapshot", QVariant::fromValue(SearchDConfig::loadSnapshot())); + // 发射信号请求工作线程执行搜索 emit requestSearch(query, m_options, searchType()); } @@ -233,6 +238,9 @@ SearchResultExpected GenericSearchEngine::doSyncSearch(const SearchQuery &query) connect(this, &GenericSearchEngine::errorOccurred, &eventLoop, &QEventLoop::quit); connect(&timeoutTimer, &QTimer::timeout, &eventLoop, &QEventLoop::quit); + // 主线程实时读取 DConfig 配置,通过参数传入工作线程 + m_options.setCustomOption("dconfig_snapshot", QVariant::fromValue(SearchDConfig::loadSnapshot())); + // 发射信号启动异步搜索 emit requestSearch(query, m_options, searchType()); diff --git a/src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp b/src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp index 3cb1db6c..6ab4fc82 100644 --- a/src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp +++ b/src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp @@ -3,6 +3,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "searchworker.h" +#include "utils/searchdconfig.h" + DFM_SEARCH_BEGIN_NS SearchWorker::SearchWorker(QObject *parent) @@ -13,6 +15,7 @@ SearchWorker::SearchWorker(QObject *parent) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); } SearchWorker::~SearchWorker() = default; diff --git a/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp b/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp index 6746fa2f..0bddf929 100644 --- a/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp +++ b/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp @@ -18,7 +18,9 @@ #include "3rdparty/fulltext/chineseanalyzer.h" #include "utils/cancellablecollector.h" #include "utils/searchutility.h" +#include "utils/searchdconfig.h" #include "utils/lucenequeryutils.h" +#include "utils/indexmutex.h" #include "utils/timerangeutils.h" DFM_SEARCH_BEGIN_NS @@ -447,6 +449,9 @@ FileNameIndexedStrategy::IndexQuery FileNameIndexedStrategy::buildIndexQuery( void FileNameIndexedStrategy::executeIndexQuery(const IndexQuery &query, const QString &searchPath, const QStringList &searchExcludedPaths) { + // 加索引级互斥锁,防止多线程并发访问同一 Lucene 索引目录 + QMutexLocker indexLock(&IndexMutexGuard::mutexForPath(m_indexDir)); + // 获取索引目录 FSDirectoryPtr directory = m_indexManager->getIndexDirectory(m_indexDir); if (!directory) { @@ -757,8 +762,9 @@ Lucene::QueryPtr FileNameIndexedStrategy::buildLuceneQuery(const IndexQuery &que } // Add path prefix query optimization + auto snapshot = m_options.customOption("dconfig_snapshot").value(); if (hasValidQuery && SearchUtility::isFilenameIndexAncestorPathsSupported() - && SearchUtility::shouldUsePathPrefixQuery(searchPath)) { + && SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) { QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath, QString::fromWCharArray(LuceneFieldNames::FileName::kAncestorPaths)); if (pathPrefixQuery) { diff --git a/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp b/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp index e561b2a6..0d588e10 100644 --- a/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp +++ b/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp @@ -23,8 +23,10 @@ #include "utils/contenthighlighter.h" #include "utils/lucenequeryutils.h" #include "utils/searchutility.h" +#include "utils/searchdconfig.h" #include "utils/lucene_cancellation_compat.h" #include "utils/timerangeutils.h" +#include "utils/indexmutex.h" using namespace Lucene; @@ -98,8 +100,9 @@ Lucene::QueryPtr OcrTextIndexedStrategy::buildLuceneQuery(const SearchQuery &que } // Add path prefix query optimization + auto snapshot = m_options.customOption("dconfig_snapshot").value(); if (mainQuery && SearchUtility::isOcrTextIndexAncestorPathsSupported() - && SearchUtility::shouldUsePathPrefixQuery(searchPath)) { + && SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) { QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath, QString::fromWCharArray(LuceneFieldNames::OcrText::kAncestorPaths)); if (pathPrefixQuery) { @@ -416,6 +419,9 @@ void OcrTextIndexedStrategy::performOcrTextSearch(const SearchQuery &query) SearchCancellationGuard guard(&m_cancelled); try { + // 加索引级互斥锁,防止多线程并发访问同一 Lucene 索引目录 + QMutexLocker indexLock(&IndexMutexGuard::mutexForPath(m_indexDir)); + // Get index directory FSDirectoryPtr directory = FSDirectory::open(m_indexDir.toStdWString()); if (!directory) { diff --git a/src/dfm-search/dfm-search-lib/utils/indexmutex.h b/src/dfm-search/dfm-search-lib/utils/indexmutex.h new file mode 100644 index 00000000..bd54e8ae --- /dev/null +++ b/src/dfm-search/dfm-search-lib/utils/indexmutex.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef INDEX_MUTEX_H +#define INDEX_MUTEX_H + +#include +#include + +DFM_SEARCH_BEGIN_NS + +class IndexMutexGuard +{ +public: + static QMutex &mutexForPath(const QString &indexDir) + { + static QMutex s_mapMutex; + static QMap s_mutexMap; + + QMutexLocker mapLock(&s_mapMutex); + auto it = s_mutexMap.find(indexDir); + if (it == s_mutexMap.end()) { + it = s_mutexMap.insert(indexDir, new QMutex()); + } + return *it.value(); + } +}; + +DFM_SEARCH_END_NS + +#endif // INDEX_MUTEX_H diff --git a/src/dfm-search/dfm-search-lib/utils/searchdconfig.cpp b/src/dfm-search/dfm-search-lib/utils/searchdconfig.cpp new file mode 100644 index 00000000..d78273ae --- /dev/null +++ b/src/dfm-search/dfm-search-lib/utils/searchdconfig.cpp @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#include "searchdconfig.h" + +#include +#include +#include +#include +#include +#include + +DFM_SEARCH_BEGIN_NS + +namespace { + +std::optional tryLoadStringListFromDConfig(const QString &appId, + const QString &schemaId, + const QString &keyName) +{ + QObject dconfigParent; + auto *dconfigPtr = Dtk::Core::DConfig::create(appId, schemaId, "", &dconfigParent); + + if (!dconfigPtr) { + qWarning() << "DConfig: Failed to create instance for appId:" << appId << "schemaId:" << schemaId; + return std::nullopt; + } + + if (!dconfigPtr->isValid()) { + qWarning() << "DConfig: Instance is invalid for appId:" << appId << "schemaId:" << schemaId; + return std::nullopt; + } + + QVariant value = dconfigPtr->value(keyName); + + if (!value.isValid()) { + qDebug() << "DConfig: Key '" << keyName << "' not found in appId:" << appId << "schemaId:" << schemaId; + return std::nullopt; + } + + if (!value.canConvert()) { + qWarning() << "DConfig: Value for key '" << keyName << "' in appId:" << appId << "schemaId:" << schemaId + << "cannot be converted to QStringList. Actual type:" << value.typeName(); + return std::nullopt; + } + + return value.toStringList(); +} + +QStringList resolveIndexedDirectories() +{ + const QString homePath = QDir::homePath(); + const QStringList fallbackResult { homePath }; + + auto dconfigNamesOpt = tryLoadStringListFromDConfig("org.deepin.anything", "org.deepin.anything", "indexing_paths"); + + if (!dconfigNamesOpt) { + qDebug() << "Failed to load indexing_paths from DConfig, using fallback."; + return fallbackResult; + } + + const QStringList &rawPathsFromDConfig = *dconfigNamesOpt; + + if (rawPathsFromDConfig.isEmpty()) { + qDebug() << "DConfig provided an empty list for indexing_paths, using fallback."; + return fallbackResult; + } + + QStringList processedPaths; + QSet uniquePathsChecker; + bool homePathEncounteredAndAdded = false; + + for (const QString &rawPath : rawPathsFromDConfig) { + QString currentPath = rawPath.trimmed(); + + if (currentPath == QLatin1String("$HOME") || currentPath == QLatin1String("~")) { + currentPath = homePath; + } else if (currentPath.startsWith(QLatin1String("$HOME/"))) { + currentPath = QDir(homePath).filePath(currentPath.mid(6)); + } else if (currentPath.startsWith(QLatin1String("~/"))) { + currentPath = QDir(homePath).filePath(currentPath.mid(2)); + } + + if (currentPath.isEmpty()) { + continue; + } + + currentPath = QDir::cleanPath(currentPath); + + if (currentPath == homePath) { + if (!homePathEncounteredAndAdded) { + processedPaths.prepend(currentPath); + uniquePathsChecker.insert(currentPath); + homePathEncounteredAndAdded = true; + } + } else { + if (!uniquePathsChecker.contains(currentPath)) { + processedPaths.append(currentPath); + uniquePathsChecker.insert(currentPath); + } + } + } + + if (homePathEncounteredAndAdded && !processedPaths.isEmpty() && processedPaths.first() != homePath) { + processedPaths.removeAll(homePath); + processedPaths.prepend(homePath); + } + + if (processedPaths.isEmpty()) { + return fallbackResult; + } + + return processedPaths; +} + +QStringList deduplicateParentChildPaths(const QStringList &dirs) +{ + if (dirs.isEmpty()) { + return QStringList(); + } + + QStringList result; + QStringList normalizedDirs; + for (const QString &dir : dirs) { + QString normalizedPath = QDir(dir).absolutePath(); + if (!normalizedPath.endsWith('/')) { + normalizedPath += '/'; + } + normalizedDirs.append(normalizedPath); + } + + std::sort(normalizedDirs.begin(), normalizedDirs.end(), + [](const QString &a, const QString &b) { return a.length() < b.length(); }); + + for (const QString ¤tPath : normalizedDirs) { + bool isSubdirectory = false; + for (const QString &addedPath : result) { + if (currentPath.startsWith(addedPath)) { + isSubdirectory = true; + break; + } + } + if (!isSubdirectory) { + QString pathToAdd = currentPath; + if (pathToAdd.length() > 1 && pathToAdd.endsWith('/')) { + pathToAdd.chop(1); + } + result.append(pathToAdd); + } + } + + return result; +} + +} // anonymous namespace + +SearchDConfigSnapshot SearchDConfig::loadSnapshot() +{ + // CRITICAL: 此调用必须在主线程中执行(DConfig 通过 DBus 读取,非线程安全) + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + + SearchDConfigSnapshot snapshot; + + // 1. 索引目录列表 + snapshot.defaultIndexedDirs = deduplicateParentChildPaths(resolveIndexedDirectories()); + + // 2. 黑名单路径 + auto blacklistOpt = tryLoadStringListFromDConfig("org.deepin.anything", "org.deepin.anything", "blacklist_paths"); + if (blacklistOpt) { + snapshot.defaultBlacklistPaths = *blacklistOpt; + } + + // 3. 支持的文件扩展名 + auto extensionsOpt = tryLoadStringListFromDConfig("org.deepin.dde.file-manager", + "org.deepin.dde.file-manager.textindex", + "supportedFileExtensions"); + if (extensionsOpt) { + snapshot.supportedFileExtensions = *extensionsOpt; + } + + return snapshot; +} + +QStringList SearchDConfig::loadFileExtensions() +{ + // CRITICAL: 此调用必须在主线程中执行(DConfig 通过 DBus 读取,非线程安全) + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + + // 3. 支持的文件扩展名 + auto extensionsOpt = tryLoadStringListFromDConfig("org.deepin.dde.file-manager", + "org.deepin.dde.file-manager.textindex", + "supportedFileExtensions"); + if (extensionsOpt.has_value()) { + return extensionsOpt.value(); + } + + return {}; +} + +QStringList SearchDConfig::loadBlacklistPaths() +{ + // CRITICAL: 此调用必须在主线程中执行(DConfig 通过 DBus 读取,非线程安全) + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + + // 2. 黑名单路径 + auto blacklistOpt = tryLoadStringListFromDConfig("org.deepin.anything", "org.deepin.anything", "blacklist_paths"); + if (blacklistOpt.has_value()) { + return blacklistOpt.value(); + } + + return {}; +} + +QStringList SearchDConfig::loadIndexedDirs() +{ + // CRITICAL: 此调用必须在主线程中执行(DConfig 通过 DBus 读取,非线程安全) + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + + // 1. 索引目录列表 + return deduplicateParentChildPaths(resolveIndexedDirectories()); +} + +DFM_SEARCH_END_NS diff --git a/src/dfm-search/dfm-search-lib/utils/searchdconfig.h b/src/dfm-search/dfm-search-lib/utils/searchdconfig.h new file mode 100644 index 00000000..fbd78fed --- /dev/null +++ b/src/dfm-search/dfm-search-lib/utils/searchdconfig.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef SEARCHDCONFIG_H +#define SEARCHDCONFIG_H + +#include +#include +#include +#include + +DFM_SEARCH_BEGIN_NS + +struct SearchDConfigSnapshot +{ + QStringList defaultIndexedDirs; + QStringList defaultBlacklistPaths; + QStringList supportedFileExtensions; +}; + +class SearchDConfig +{ +public: + static SearchDConfigSnapshot loadSnapshot(); + static QStringList loadFileExtensions(); + static QStringList loadBlacklistPaths(); + static QStringList loadIndexedDirs(); +}; + +DFM_SEARCH_END_NS + +Q_DECLARE_METATYPE(DFMSEARCH::SearchDConfigSnapshot) + +#endif // SEARCHDCONFIG_H diff --git a/src/dfm-search/dfm-search-lib/utils/searchutility.cpp b/src/dfm-search/dfm-search-lib/utils/searchutility.cpp index c960bd58..624ff198 100644 --- a/src/dfm-search/dfm-search-lib/utils/searchutility.cpp +++ b/src/dfm-search/dfm-search-lib/utils/searchutility.cpp @@ -3,11 +3,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "searchutility.h" #include "filenameblacklistmatcher.h" +#include "searchdconfig.h" #include -#include - #include #include #include @@ -98,121 +97,6 @@ static bool isPinyinSequenceHelper(const QString &str, int startPos, const QSet< return false; } -// This function is internal to this unit (static) and handles the core DConfig loading. -static std::optional tryLoadStringListFromDConfigInternal( - const QString &appId, - const QString &schemaId, - const QString &keyName) -{ - // Dtk::Core::DConfig::create returns a pointer and ideally needs a parent - // for memory management. For a short-lived object within a function, - // a local QObject can serve as a temporary parent. - QObject dconfigParent; // Temporary parent for the DConfig instance - Dtk::Core::DConfig *dconfigPtr = Dtk::Core::DConfig::create(appId, schemaId, "", &dconfigParent); - - // Check if DConfig object was created successfully - if (!dconfigPtr) { - qWarning() << "DConfig: Failed to create DConfig instance for appId:" << appId << "schemaId:" << schemaId; - return std::nullopt; - } - - // Check if the created DConfig instance is valid - if (!dconfigPtr->isValid()) { - qWarning() << "DConfig: Instance is invalid for appId:" << appId << "schemaId:" << schemaId; - // No need to delete dconfigPtr, dconfigParent will manage it. - return std::nullopt; - } - - QVariant value = dconfigPtr->value(keyName); - - if (!value.isValid()) { - qDebug() << "DConfig: Key '" << keyName << "' not found in appId:" << appId << "schemaId:" << schemaId; - return std::nullopt; // Key not found - } - - if (!value.canConvert()) { - qWarning() << "DConfig: Value for key '" << keyName << "' in appId:" << appId << "schemaId:" << schemaId - << "cannot be converted to QStringList. Actual type:" << value.typeName(); - return std::nullopt; // Type mismatch - } - // No need to delete dconfigPtr, dconfigParent will manage it when it goes out of scope. - return value.toStringList(); -} - -// --- Specific Loader for "supportedFileExtensions" --- -static std::optional> tryLoadSupportedFileExtensionsFromDConfig() -{ - const QString appId = "org.deepin.dde.file-manager"; // Assuming this is the correct appId - const QString schemaId = "org.deepin.dde.file-manager.textindex"; - const QString keyName = "supportedFileExtensions"; - - std::optional stringListOpt = tryLoadStringListFromDConfigInternal(appId, schemaId, keyName); - - if (!stringListOpt) { - return std::nullopt; // Loading failed at the generic level - } - - const QStringList &extensionsFromDConfigList = *stringListOpt; - QSet extensionsSet; - for (const QString &ext : extensionsFromDConfigList) { - QString cleanedExt = ext.startsWith('.') ? ext.mid(1) : ext; - if (!cleanedExt.isEmpty()) { - extensionsSet.insert(cleanedExt); - } - } - - if (extensionsFromDConfigList.isEmpty()) { - qDebug() << "DConfig: Key '" << keyName << "' in schema '" << schemaId << "' provided an empty list."; - } else if (extensionsSet.isEmpty() && !extensionsFromDConfigList.isEmpty()) { - qDebug() << "DConfig: Key '" << keyName << "' in schema '" << schemaId << "' contained only invalid/empty entries after cleaning."; - } else if (!extensionsSet.isEmpty()) { - qDebug() << "DConfig: Successfully processed" << extensionsSet.size() << "supported extensions from DConfig."; - } - return extensionsSet; // Return the processed set (might be empty if DConfig list was empty or all invalid) -} - -// --- Specific Loader for "indexing_paths" --- -static std::optional tryLoadIndexingNamesFromDConfig() -{ - const QString appId = "org.deepin.anything"; - const QString schemaId = "org.deepin.anything"; // As per your requirement - const QString keyName = "indexing_paths"; - - std::optional stringListOpt = tryLoadStringListFromDConfigInternal(appId, schemaId, keyName); - - if (!stringListOpt) { - return std::nullopt; // Loading failed - } - - const QStringList &namesFromDConfigList = *stringListOpt; - - if (namesFromDConfigList.isEmpty()) { - qDebug() << "DConfig: Key '" << keyName << "' in schema '" << schemaId << "' provided an empty list."; - } - return namesFromDConfigList; // Return the processed set -} - -// --- Specific Loader for "blacklist_paths" --- -static std::optional tryLoadBlacklistPathsFromDConfig() -{ - const QString appId = "org.deepin.anything"; - const QString schemaId = "org.deepin.anything"; - const QString keyName = "blacklist_paths"; - - std::optional stringListOpt = tryLoadStringListFromDConfigInternal(appId, schemaId, keyName); - - if (!stringListOpt) { - return std::nullopt; // Loading failed - } - - const QStringList &pathsFromDConfigList = *stringListOpt; - - if (pathsFromDConfigList.isEmpty()) { - qDebug() << "DConfig: Key '" << keyName << "' in schema '" << schemaId << "' provided an empty list."; - } - return pathsFromDConfigList; // Return the processed list -} - // 辅助函数:提供硬编码的默认扩展名集合 static const QSet &getDefaultSupportedExtensions() { @@ -235,7 +119,7 @@ static const QSet &supportedExtensions() // 静态局部变量的初始化只发生一次,并且是线程安全的。 // 使用立即调用的lambda表达式来执行初始化逻辑。 static const QSet kResolvedExtensions = []() -> QSet { - std::optional> dconfigExtensionsOpt = tryLoadSupportedFileExtensionsFromDConfig(); + std::optional> dconfigExtensionsOpt = SearchDConfig::loadFileExtensions().toSet(); // 如果DConfig成功返回了一个【非空】的集合,则使用它 if (dconfigExtensionsOpt && !dconfigExtensionsOpt->isEmpty()) { @@ -255,99 +139,6 @@ static const QSet &supportedExtensions() return kResolvedExtensions; } -static QStringList getResolvedIndexedDirectories() // Renamed for clarity -{ - const QString homePath = QDir::homePath(); // Cache for frequent use - const QStringList fallbackResult { homePath }; - - std::optional dconfigNamesOpt = tryLoadIndexingNamesFromDConfig(); - - if (!dconfigNamesOpt) { - qDebug() << "Failed to load indexing names from DConfig or DConfig instance invalid, using fallback."; - return fallbackResult; - } - - const QStringList &rawPathsFromDConfig = *dconfigNamesOpt; - - if (rawPathsFromDConfig.isEmpty()) { - qDebug() << "DConfig provided an empty list for indexing names, using fallback."; - return fallbackResult; - } - - QList processedPaths; // Use QList to maintain order before final sort - QSet uniquePathsChecker; // To ensure uniqueness during processing - - // Special handling for homePath if it's intended to be first - bool homePathEncounteredAndAdded = false; - - for (const QString &rawPath : rawPathsFromDConfig) { - QString currentPath = rawPath.trimmed(); // Remove leading/trailing whitespace - - // 1. Expand environment variables (specifically $HOME and ~) - if (currentPath == QLatin1String("$HOME") || currentPath == QLatin1String("~")) { - currentPath = homePath; - } else if (currentPath.startsWith(QLatin1String("$HOME/"))) { - currentPath = QDir(homePath).filePath(currentPath.mid(6)); // Length of "$HOME/" - } else if (currentPath.startsWith(QLatin1String("~/"))) { - currentPath = QDir(homePath).filePath(currentPath.mid(2)); // Length of "~/" - } - // Add more generic environment variable expansion here if needed: - // e.g., using QProcessEnvironment::systemEnvironment().value("VAR_NAME") - // or a regex for ${VAR_NAME} patterns. - - // 2. Normalize and validate path (basic validation: non-empty) - if (currentPath.isEmpty()) { - qDebug() << "Skipping empty path string after processing raw entry:" << rawPath; - continue; - } - - currentPath = QDir::cleanPath(currentPath); // Normalize path separators (e.g. \\ to /) - - // Further validation could be done here, e.g., check if it's an absolute path - // QFileInfo fileInfo(currentPath); - // if (!fileInfo.isAbsolute()) { - // qWarning() << "Skipping relative path:" << currentPath; - // continue; - // } - // if (!fileInfo.isDir() || !fileInfo.exists()) { // Stricter: must be an existing directory - // qWarning() << "Skipping non-existent or non-directory path:" << currentPath; - // continue; - // } - - // 3. Add to list if unique and handle homePath ordering - if (currentPath == homePath) { - if (!homePathEncounteredAndAdded) { - processedPaths.prepend(currentPath); // Ensure homePath is first if encountered - uniquePathsChecker.insert(currentPath); - homePathEncounteredAndAdded = true; - } - // If homePath was already added (e.g. from a previous "$HOME"), skip subsequent identical entries. - // The uniquePathsChecker will also prevent adding it again if it wasn't the first home path encountered. - } else { - if (!uniquePathsChecker.contains(currentPath)) { - processedPaths.append(currentPath); - uniquePathsChecker.insert(currentPath); - } - } - } - - // Ensure home path is first if it was added but not via prepend (e.g. if it wasn't the first item processed) - // This can happen if DConfig has ["/foo", "$HOME"] and homePath wasn't prepended. - // The above loop tries to prepend, but this is a safeguard if the logic changes or has subtle bugs. - if (homePathEncounteredAndAdded && !processedPaths.isEmpty() && processedPaths.first() != homePath) { - processedPaths.removeAll(homePath); // Remove from other positions - processedPaths.prepend(homePath); // Add to front - } - - if (processedPaths.isEmpty()) { - qDebug() << "All paths from DConfig were invalid or empty after processing, using fallback."; - return fallbackResult; - } - - qDebug() << "Resolved indexed directories:" << processedPaths; - return processedPaths; -} - bool isPinyinSequence(const QString &input) { if (input.isEmpty()) @@ -514,59 +305,12 @@ QStringList defaultContentSearchExtensions() QStringList defaultIndexedDirectory() { - const QStringList &dirs = getResolvedIndexedDirectories(); - - if (dirs.isEmpty()) { - return QStringList(); - } - - // 创建一个新列表来存储结果 - QStringList result; - - // 先将所有路径规范化并排序,以便父目录出现在子目录之前 - QStringList normalizedDirs; - for (const QString &dir : dirs) { - QString normalizedPath = QDir(dir).absolutePath(); - // 确保路径以 '/' 结尾,便于后续比较 - if (!normalizedPath.endsWith('/')) { - normalizedPath += '/'; - } - normalizedDirs.append(normalizedPath); - } - - // 排序,确保短路径(潜在的父路径)优先 - std::sort(normalizedDirs.begin(), normalizedDirs.end(), - [](const QString &a, const QString &b) { return a.length() < b.length(); }); - - // 检查路径之间的父子关系 - for (const QString ¤tPath : normalizedDirs) { - bool isSubdirectory = false; - - // 检查当前路径是否是已添加路径的子目录 - for (const QString &addedPath : result) { - if (currentPath.startsWith(addedPath)) { - isSubdirectory = true; - break; - } - } - - // 如果不是子目录,则添加到结果中 - if (!isSubdirectory) { - // 移除末尾的 '/',除非是根目录 "/" - QString pathToAdd = currentPath; - if (pathToAdd.length() > 1 && pathToAdd.endsWith('/')) { - pathToAdd.chop(1); - } - result.append(pathToAdd); - } - } - - return result; + return SearchDConfig::loadIndexedDirs(); } QStringList defaultBlacklistPaths() { - std::optional dconfigPathsOpt = tryLoadBlacklistPathsFromDConfig(); + std::optional dconfigPathsOpt = SearchDConfig::loadBlacklistPaths(); if (!dconfigPathsOpt) { qDebug() << "Failed to load blacklist paths from DConfig or DConfig instance invalid, returning empty list."; @@ -898,5 +642,23 @@ bool shouldUsePathPrefixQuery(const QString &searchPath) return true; } +bool shouldUsePathPrefixQuery(const QString &searchPath, const QStringList &defaultDirs) +{ + if (searchPath == "/" || searchPath.isEmpty()) { + return false; + } + + for (const QString &defaultDir : defaultDirs) { + QString normalizedDefault = QDir::cleanPath(defaultDir); + QString normalizedSearch = QDir::cleanPath(searchPath); + + if (normalizedSearch == normalizedDefault) { + return false; + } + } + + return true; +} + } // namespace SearchUtility DFM_SEARCH_END_NS diff --git a/src/dfm-search/dfm-search-lib/utils/searchutility.h b/src/dfm-search/dfm-search-lib/utils/searchutility.h index 1fa74fd8..bb34af18 100644 --- a/src/dfm-search/dfm-search-lib/utils/searchutility.h +++ b/src/dfm-search/dfm-search-lib/utils/searchutility.h @@ -40,6 +40,14 @@ QStringList deepinAnythingFileTypes(); */ bool shouldUsePathPrefixQuery(const QString &searchPath); +/** + * @brief Thread-safe version of shouldUsePathPrefixQuery using preloaded config + * @param searchPath The search path + * @param defaultDirs Preloaded default indexed directories (from SearchDConfigSnapshot) + * @return true if path prefix query should be used, false otherwise + */ +bool shouldUsePathPrefixQuery(const QString &searchPath, const QStringList &defaultDirs); + /** * @brief Check if the filename index supports the ancestor_paths field. * This function checks the filename index version and returns true if the version is greater than 3.