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.