Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@

#include "3rdparty/fulltext/chineseanalyzer.h"
#include "utils/cancellablecollector.h"
#include "utils/contenthighlighter.h"

Check warning on line 24 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/contenthighlighter.h" not found.

Check warning on line 24 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/contenthighlighter.h" not found.
#include "utils/lucenequeryutils.h"

Check warning on line 25 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/lucenequeryutils.h" not found.

Check warning on line 25 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/lucenequeryutils.h" not found.
#include "utils/searchutility.h"

Check warning on line 26 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/searchutility.h" not found.

Check warning on line 26 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/searchutility.h" not found.
#include "utils/searchdconfig.h"

Check warning on line 27 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/searchdconfig.h" not found.

Check warning on line 27 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/searchdconfig.h" not found.
#include "utils/lucene_cancellation_compat.h"

Check warning on line 28 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/lucene_cancellation_compat.h" not found.

Check warning on line 28 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/lucene_cancellation_compat.h" not found.
#include "utils/timerangeutils.h"

Check warning on line 29 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/timerangeutils.h" not found.

Check warning on line 29 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/timerangeutils.h" not found.
#include "utils/indexmutex.h"

Check warning on line 30 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/indexmutex.h" not found.

Check warning on line 30 in src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/indexmutex.h" not found.

using namespace Lucene;

Expand Down Expand Up @@ -101,8 +103,9 @@
}

// Add path prefix query optimization
auto snapshot = m_options.customOption("dconfig_snapshot").value<SearchDConfigSnapshot>();
if (mainQuery && SearchUtility::isContentIndexAncestorPathsSupported()
&& SearchUtility::shouldUsePathPrefixQuery(searchPath)) {
&& SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) {
QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath,
QString::fromWCharArray(LuceneFieldNames::Content::kAncestorPaths));
if (pathPrefixQuery) {
Expand Down Expand Up @@ -417,6 +420,10 @@
SearchCancellationGuard guard(&m_cancelled);

try {
// 加索引级互斥锁,防止多线程并发访问同一 Lucene 索引目录
// FSDirectory::open() 对相同路径返回缓存实例,底层 IndexInput 缓冲区共享
QMutexLocker indexLock(&IndexMutexGuard::mutexForPath(m_indexDir));

// 获取索引目录
FSDirectoryPtr directory = FSDirectory::open(m_indexDir.toStdWString());
if (!directory) {
Expand Down
8 changes: 8 additions & 0 deletions src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

#include <QMutexLocker>
#include <QFileInfo>
#include <QEventLoop>

Check warning on line 8 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QEventLoop> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 8 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QEventLoop> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTimer>

Check warning on line 9 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 9 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include "utils/searchdconfig.h"

Check warning on line 11 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "utils/searchdconfig.h" not found.

Check warning on line 11 in src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "utils/searchdconfig.h" not found.

DFM_SEARCH_BEGIN_NS
DCORE_USE_NAMESPACE

Expand Down Expand Up @@ -116,6 +118,9 @@
return;
}

// 主线程实时读取 DConfig 配置,通过参数传入工作线程
m_options.setCustomOption("dconfig_snapshot", QVariant::fromValue(SearchDConfig::loadSnapshot()));

// 发射信号请求工作线程执行搜索
emit requestSearch(query, m_options, searchType());
}
Expand Down Expand Up @@ -233,6 +238,9 @@
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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -13,6 +15,7 @@ SearchWorker::SearchWorker(QObject *parent)
qRegisterMetaType<SearchType>();
qRegisterMetaType<SearchResultList>();
qRegisterMetaType<SearchError>();
qRegisterMetaType<SearchDConfigSnapshot>();
}

SearchWorker::~SearchWorker() = default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -757,8 +762,9 @@ Lucene::QueryPtr FileNameIndexedStrategy::buildLuceneQuery(const IndexQuery &que
}

// Add path prefix query optimization
auto snapshot = m_options.customOption("dconfig_snapshot").value<SearchDConfigSnapshot>();
if (hasValidQuery && SearchUtility::isFilenameIndexAncestorPathsSupported()
&& SearchUtility::shouldUsePathPrefixQuery(searchPath)) {
&& SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) {
QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath,
QString::fromWCharArray(LuceneFieldNames::FileName::kAncestorPaths));
if (pathPrefixQuery) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -98,8 +100,9 @@ Lucene::QueryPtr OcrTextIndexedStrategy::buildLuceneQuery(const SearchQuery &que
}

// Add path prefix query optimization
auto snapshot = m_options.customOption("dconfig_snapshot").value<SearchDConfigSnapshot>();
if (mainQuery && SearchUtility::isOcrTextIndexAncestorPathsSupported()
&& SearchUtility::shouldUsePathPrefixQuery(searchPath)) {
&& SearchUtility::shouldUsePathPrefixQuery(searchPath, snapshot.defaultIndexedDirs)) {
QueryPtr pathPrefixQuery = LuceneQueryUtils::buildPathPrefixQuery(searchPath,
QString::fromWCharArray(LuceneFieldNames::OcrText::kAncestorPaths));
if (pathPrefixQuery) {
Expand Down Expand Up @@ -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) {
Expand Down
31 changes: 31 additions & 0 deletions src/dfm-search/dfm-search-lib/utils/indexmutex.h
Original file line number Diff line number Diff line change
@@ -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 <QMutex>
#include <QMap>

DFM_SEARCH_BEGIN_NS

class IndexMutexGuard
{
public:
static QMutex &mutexForPath(const QString &indexDir)
{
static QMutex s_mapMutex;
static QMap<QString, QMutex *> 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
223 changes: 223 additions & 0 deletions src/dfm-search/dfm-search-lib/utils/searchdconfig.cpp
Original file line number Diff line number Diff line change
@@ -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 <DConfig>
#include <QDebug>
#include <QDir>
#include <QObject>
#include <QThread>
#include <QCoreApplication>

DFM_SEARCH_BEGIN_NS

namespace {

std::optional<QStringList> 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<QStringList>()) {
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<QString> 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 &currentPath : 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
Loading
Loading