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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions autotests/dfm-search-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ target_sources(dfm-search-test PRIVATE
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/size_parser.cpp
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/time_parser.cpp
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/cli_options.cpp
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/preview_command.cpp
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/preview_output_utils.cpp
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/output/output_formatter.h
${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/output/text_output.cpp
Expand Down
33 changes: 33 additions & 0 deletions autotests/dfm-search-tests/tst_content_retriever.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ private Q_SLOTS:
void fetchPreview_noKeyword();
void fetchPreview_withKeyword();
void fetchPreview_keywordNotFound();
void fetchPreview_notIndexed();
void fetchPreview_offsetBeyondContent();
void fetchPreview_unlimitedNoKeyword();
void previewSnippet_basic();
Expand Down Expand Up @@ -253,6 +254,7 @@ void tst_ContentRetriever::fetchPreview_noKeyword()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QCOMPARE(result.content(), QString("world"));
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), -1);
Expand All @@ -279,6 +281,7 @@ void tst_ContentRetriever::fetchPreview_withKeyword()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QCOMPARE(result.content(), QString("world from"));
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), 6);
Expand All @@ -305,11 +308,38 @@ void tst_ContentRetriever::fetchPreview_keywordNotFound()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QVERIFY(result.content().isEmpty());
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), -1);
}

void tst_ContentRetriever::fetchPreview_notIndexed()
{
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());

const QString contentIndexDir = tempDir.path() + "/content-index";
createIndex(contentIndexDir, SearchType::Content);

ContentRetriever retriever;
retriever.setIndexDirectory(SearchType::Content, contentIndexDir);

const PreviewResult missingPathResult = retriever.fetchPreview("/tmp/missing.txt",
SearchType::Content);
QVERIFY(missingPathResult.status() == PreviewStatus::NotIndexed);
QVERIFY(missingPathResult.content().isEmpty());
QCOMPARE(missingPathResult.charCount(), 0);
QCOMPARE(missingPathResult.keywordOffset(), -1);

const PreviewResult unsupportedSemanticResult = retriever.fetchPreview("/tmp/package.deb",
SearchType::Semantic);
QVERIFY(unsupportedSemanticResult.status() == PreviewStatus::NotIndexed);
QVERIFY(unsupportedSemanticResult.content().isEmpty());
QCOMPARE(unsupportedSemanticResult.charCount(), 0);
QCOMPARE(unsupportedSemanticResult.keywordOffset(), -1);
}

void tst_ContentRetriever::fetchPreview_offsetBeyondContent()
{
QTemporaryDir tempDir;
Expand All @@ -330,6 +360,7 @@ void tst_ContentRetriever::fetchPreview_offsetBeyondContent()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QVERIFY(result.content().isEmpty());
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), -1);
Expand Down Expand Up @@ -357,6 +388,7 @@ void tst_ContentRetriever::fetchPreview_unlimitedNoKeyword()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QCOMPARE(result.content(), QString("hello world from content index"));
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), -1);
Expand All @@ -372,6 +404,7 @@ void tst_ContentRetriever::fetchPreview_unlimitedNoKeyword()
const PreviewResult result = retriever.fetchPreview("/tmp/doc-a.txt",
SearchType::Content,
options);
QVERIFY(result.status() == PreviewStatus::Success);
QCOMPARE(result.content(), QString("world from content index"));
QCOMPARE(result.charCount(), QString("hello world from content index").size());
QCOMPARE(result.keywordOffset(), -1);
Expand Down
27 changes: 27 additions & 0 deletions autotests/dfm-search-tests/tst_search_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@
#include <lucene++/KeywordAnalyzer.h>
#include <lucene++/LuceneHeaders.h>

#include "output/json_output.h"

Check warning on line 32 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "output/json_output.h" not found.

Check warning on line 32 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "output/json_output.h" not found.
#include "output/text_output.h"

Check warning on line 33 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "output/text_output.h" not found.

Check warning on line 33 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "output/text_output.h" not found.
#include "preview_command.h"

Check warning on line 34 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "preview_command.h" not found.

Check warning on line 34 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "preview_command.h" not found.
#include "preview_output_utils.h"

Check warning on line 35 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "preview_output_utils.h" not found.

Check warning on line 35 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: "preview_output_utils.h" not found.

using namespace DFMSEARCH;
using namespace Lucene;
Expand Down Expand Up @@ -120,8 +121,9 @@
{
Q_OBJECT

private Q_SLOTS:

Check warning on line 124 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

There is an unknown macro here somewhere. Configuration is required. If Q_SLOTS is a macro then please configure it.

Check warning on line 124 in autotests/dfm-search-tests/tst_search_output.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

There is an unknown macro here somewhere. Configuration is required. If Q_SLOTS is a macro then please configure it.
void previewOutputHelpers_includeCharCount();
void previewCommand_failsForUnindexedPath();
void jsonOutput_contentAndFilenameCharCountContract();
void jsonOutput_ocrAndSemanticCharCountContract();
void textOutput_contentAndSemanticCharCountContract();
Expand Down Expand Up @@ -156,6 +158,31 @@
QVERIFY(text.contains("world from"));
}

void tst_SearchOutput::previewCommand_failsForUnindexedPath()
{
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());

const QString contentIndexDir = tempDir.path() + "/content-index";
createIndex(contentIndexDir, SearchType::Content);

ContentRetriever retriever;
retriever.setIndexDirectory(SearchType::Content, contentIndexDir);

SearchCliConfig config;
config.subcommand = "preview";
config.searchType = SearchType::Content;
config.searchPath = "/tmp/doc-a.txt,/tmp/missing.txt";
config.jsonOutput = true;

const PreviewCommandResult result = dfmsearch::runPreviewCommand(config, retriever);
QCOMPARE(result.exitCode, 1);
QVERIFY(result.stdoutText.isEmpty());
QVERIFY(result.stderrText.contains("Error: preview requires indexed content for:"));
QVERIFY(result.stderrText.contains("/tmp/missing.txt"));
QVERIFY(!result.stderrText.contains("/tmp/doc-a.txt"));
}

void tst_SearchOutput::jsonOutput_contentAndFilenameCharCountContract()
{
SearchOptions options;
Expand Down
10 changes: 8 additions & 2 deletions include/dfm-search/dfm-search/contentretriever.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,16 @@ class PreviewOptions
QSharedDataPointer<PreviewOptionsPrivate> d;
};

enum class PreviewStatus {
Success = 0,
NotIndexed = 1,
};

/**
* @brief Result of a preview extraction (Pimpl-based, ABI-stable)
*
* Contains the extracted content snippet and the position of the keyword
* in the full document text (-1 if no keyword or not matched).
* Contains the extraction status, content snippet, and the position of the
* keyword in the full document text (-1 if no keyword or not matched).
*
* Supports implicit sharing (COW) via QSharedDataPointer.
*/
Expand All @@ -98,6 +103,7 @@ class PreviewResult
PreviewResult(PreviewResult &&other) noexcept = default;
PreviewResult &operator=(PreviewResult &&other) noexcept = default;

PreviewStatus status() const;
QString content() const;
int charCount() const;
int keywordOffset() const;
Expand Down
2 changes: 2 additions & 0 deletions src/dfm-search/dfm-search-client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ set(SRCS
main.cpp
cli_options.cpp
cli_options.h
preview_command.cpp
preview_command.h
preview_output_utils.cpp
preview_output_utils.h
time_parser.cpp
Expand Down
58 changes: 7 additions & 51 deletions src/dfm-search/dfm-search-client/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QCoreApplication>

Check warning on line 5 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 5 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

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

Check warning on line 6 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 6 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QTextStream>

#include <dfm-search/searchengine.h>

Check warning on line 8 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/searchengine.h> 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-client/main.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <dfm-search/searchengine.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search/searchfactory.h>

Check warning on line 9 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/searchfactory.h> 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-client/main.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <dfm-search/searchfactory.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search/searchquery.h>
#include <dfm-search/searchoptions.h>
#include <dfm-search/filenamesearchapi.h>
#include <dfm-search/contentsearchapi.h>
#include <dfm-search/ocrtextsearchapi.h>
#include <dfm-search/semanticsearcher.h>
#include <dfm-search/contentretriever.h>

Check warning on line 16 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/contentretriever.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 16 in src/dfm-search/dfm-search-client/main.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <dfm-search/contentretriever.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include "cli_options.h"
#include "preview_output_utils.h"
#include "preview_command.h"
#include "output/text_output.h"
#include "output/json_output.h"

Expand Down Expand Up @@ -197,54 +193,14 @@
// Preview subcommand: fetch content preview on demand
if (config.subcommand == "preview") {
DFMSEARCH::ContentRetriever retriever;
DFMSEARCH::PreviewOptions previewOptions;
previewOptions.setOffset(config.offset);
// 无 keyword 且未显式设置 --max-preview:maxLength=0 表示无限制(返回全文)
// 有 keyword 或显式设置了 --max-preview:使用 maxPreviewLength 值
if (config.keyword.isEmpty() && !config.maxPreviewSet) {
previewOptions.setMaxLength(0);
} else {
previewOptions.setMaxLength(config.maxPreviewLength);
const PreviewCommandResult previewResult = runPreviewCommand(config, retriever);
if (!previewResult.stderrText.isEmpty()) {
std::cerr << previewResult.stderrText.toStdString();
}
previewOptions.setKeyword(config.keyword);

// Paths are stored as comma-separated in config.searchPath
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList paths = config.searchPath.split(',', Qt::SkipEmptyParts);
#else
QStringList paths = config.searchPath.split(',', QString::SkipEmptyParts);
#endif

if (config.jsonOutput) {
// JSON output
QJsonObject root;
root["type"] = "preview";
root["searchType"] = (config.searchType == SearchType::Content) ? "content"
: (config.searchType == SearchType::Ocr) ? "ocr" : "semantic";
root["keyword"] = config.keyword;
root["offset"] = config.offset;
root["maxLength"] = previewOptions.maxLength();

QJsonArray results;
for (const QString &path : paths) {
DFMSEARCH::PreviewResult result = retriever.fetchPreview(path, config.searchType, previewOptions);
results.append(previewResultToJson(path, result));
}

root["totalResults"] = results.size();
root["results"] = results;

QJsonDocument doc(root);
std::cout << doc.toJson(QJsonDocument::Indented).toStdString() << std::endl;
} else {
// Text output
QTextStream out(stdout);
for (const QString &path : paths) {
DFMSEARCH::PreviewResult result = retriever.fetchPreview(path, config.searchType, previewOptions);
writePreviewResultText(out, path, result);
}
if (!previewResult.stdoutText.isEmpty()) {
std::cout << previewResult.stdoutText.toStdString();
}
return 0;
return previewResult.exitCode;
}

// Semantic search mode
Expand Down
Loading
Loading