diff --git a/autotests/dfm-search-tests/main.cpp b/autotests/dfm-search-tests/main.cpp index 2728f95a..59a2f42e 100644 --- a/autotests/dfm-search-tests/main.cpp +++ b/autotests/dfm-search-tests/main.cpp @@ -4,15 +4,16 @@ #include +// Test object creation functions are defined in their respective .cpp files +extern QObject *create_tst_DfmSearch(); +extern QObject *create_tst_SearchUtils(); +extern QObject *create_tst_TimeRangeFilter(); +extern QObject *create_tst_TextSearchAPI(); + int main(int argc, char *argv[]) { int result = 0; - // Test object creation functions are defined in their respective .cpp files - extern QObject *create_tst_DfmSearch(); - extern QObject *create_tst_SearchUtils(); - extern QObject *create_tst_TimeRangeFilter(); - // Run all test objects QObject *testObj1 = create_tst_DfmSearch(); result |= QTest::qExec(testObj1, argc, argv); @@ -26,5 +27,9 @@ int main(int argc, char *argv[]) result |= QTest::qExec(testObj3, argc, argv); delete testObj3; + QObject *testObj4 = create_tst_TextSearchAPI(); + result |= QTest::qExec(testObj4, argc, argv); + delete testObj4; + return result; } diff --git a/autotests/dfm-search-tests/tst_textsearch_api.cpp b/autotests/dfm-search-tests/tst_textsearch_api.cpp new file mode 100644 index 00000000..92dec9e9 --- /dev/null +++ b/autotests/dfm-search-tests/tst_textsearch_api.cpp @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include + +using namespace DFMSEARCH; + +class tst_TextSearchAPI : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + // TextSearchOptionsAPI tests + void textSearchOptions_defaultValues(); + void textSearchOptions_setMaxPreviewLength(); + void textSearchOptions_setSearchResultHighlightEnabled(); + void textSearchOptions_setFullTextRetrievalEnabled(); + + // TextSearchResultAPI tests + void textSearchResult_highlightedContent(); + void textSearchResult_filename(); + void textSearchResult_isHidden(); + void textSearchResult_modifyTimestamp(); + void textSearchResult_birthTimestamp(); + + // ContentOptionsAPI tests + void contentOptions_inheritance(); + void contentOptions_setFilenameContentMixedAndSearchEnabled(); + + // ContentResultAPI tests + void contentResult_inheritance(); + + // OcrTextOptionsAPI tests + void ocrTextOptions_inheritance(); + void ocrTextOptions_defaultValues(); + void ocrTextOptions_setFilenameOcrContentMixedAndSearchEnabled(); + + // OcrTextResultAPI tests + void ocrTextResult_inheritance(); + void ocrTextResult_ocrContent(); +}; + +void tst_TextSearchAPI::initTestCase() +{ +} + +void tst_TextSearchAPI::cleanupTestCase() +{ +} + +// ==================== TextSearchOptionsAPI Tests ==================== + +void tst_TextSearchAPI::textSearchOptions_defaultValues() +{ + SearchOptions options; + TextSearchOptionsAPI api(options); + + // 默认值在构造函数中设置,但基类构造函数不设置默认值 + // 默认值由子类设置 + QCOMPARE(api.maxPreviewLength(), 0); + QCOMPARE(api.isSearchResultHighlightEnabled(), false); + QCOMPARE(api.isFullTextRetrievalEnabled(), false); +} + +void tst_TextSearchAPI::textSearchOptions_setMaxPreviewLength() +{ + SearchOptions options; + TextSearchOptionsAPI api(options); + + api.setMaxPreviewLength(100); + QCOMPARE(api.maxPreviewLength(), 100); + + api.setMaxPreviewLength(500); + QCOMPARE(api.maxPreviewLength(), 500); + + api.setMaxPreviewLength(0); + QCOMPARE(api.maxPreviewLength(), 0); +} + +void tst_TextSearchAPI::textSearchOptions_setSearchResultHighlightEnabled() +{ + SearchOptions options; + TextSearchOptionsAPI api(options); + + api.setSearchResultHighlightEnabled(true); + QCOMPARE(api.isSearchResultHighlightEnabled(), true); + + api.setSearchResultHighlightEnabled(false); + QCOMPARE(api.isSearchResultHighlightEnabled(), false); +} + +void tst_TextSearchAPI::textSearchOptions_setFullTextRetrievalEnabled() +{ + SearchOptions options; + TextSearchOptionsAPI api(options); + + api.setFullTextRetrievalEnabled(true); + QCOMPARE(api.isFullTextRetrievalEnabled(), true); + + api.setFullTextRetrievalEnabled(false); + QCOMPARE(api.isFullTextRetrievalEnabled(), false); +} + +// ==================== TextSearchResultAPI Tests ==================== + +void tst_TextSearchAPI::textSearchResult_highlightedContent() +{ + SearchResult result("/test/path"); + TextSearchResultAPI api(result); + + QVERIFY(api.highlightedContent().isEmpty()); + + api.setHighlightedContent("test highlighted content"); + QCOMPARE(api.highlightedContent(), QString("test highlighted content")); + + api.setHighlightedContent(""); + QVERIFY(api.highlightedContent().isEmpty()); +} + +void tst_TextSearchAPI::textSearchResult_filename() +{ + SearchResult result("/test/path"); + TextSearchResultAPI api(result); + + QVERIFY(api.filename().isEmpty()); + + api.setFilename("test.txt"); + QCOMPARE(api.filename(), QString("test.txt")); + + api.setFilename(""); + QVERIFY(api.filename().isEmpty()); +} + +void tst_TextSearchAPI::textSearchResult_isHidden() +{ + SearchResult result("/test/path"); + TextSearchResultAPI api(result); + + QCOMPARE(api.isHidden(), false); + + api.setIsHidden(true); + QCOMPARE(api.isHidden(), true); + + api.setIsHidden(false); + QCOMPARE(api.isHidden(), false); +} + +void tst_TextSearchAPI::textSearchResult_modifyTimestamp() +{ + SearchResult result("/test/path"); + TextSearchResultAPI api(result); + + QCOMPARE(api.modifyTimestamp(), 0); + + api.setModifyTimestamp(1700000000); + QCOMPARE(api.modifyTimestamp(), 1700000000); + QVERIFY(!api.modifyTimeString().isEmpty()); + + api.setModifyTimestamp(0); + QCOMPARE(api.modifyTimestamp(), 0); +} + +void tst_TextSearchAPI::textSearchResult_birthTimestamp() +{ + SearchResult result("/test/path"); + TextSearchResultAPI api(result); + + QCOMPARE(api.birthTimestamp(), 0); + + api.setBirthTimestamp(1600000000); + QCOMPARE(api.birthTimestamp(), 1600000000); + QVERIFY(!api.birthTimeString().isEmpty()); + + api.setBirthTimestamp(0); + QCOMPARE(api.birthTimestamp(), 0); +} + +// ==================== ContentOptionsAPI Tests ==================== + +void tst_TextSearchAPI::contentOptions_inheritance() +{ + SearchOptions options; + ContentOptionsAPI api(options); + + // 验证继承自 TextSearchOptionsAPI + api.setMaxPreviewLength(300); + QCOMPARE(api.maxPreviewLength(), 300); + + api.setSearchResultHighlightEnabled(true); + QCOMPARE(api.isSearchResultHighlightEnabled(), true); + + api.setFullTextRetrievalEnabled(false); + QCOMPARE(api.isFullTextRetrievalEnabled(), false); +} + +void tst_TextSearchAPI::contentOptions_setFilenameContentMixedAndSearchEnabled() +{ + SearchOptions options; + ContentOptionsAPI api(options); + + QCOMPARE(api.isFilenameContentMixedAndSearchEnabled(), false); + + api.setFilenameContentMixedAndSearchEnabled(true); + QCOMPARE(api.isFilenameContentMixedAndSearchEnabled(), true); + + api.setFilenameContentMixedAndSearchEnabled(false); + QCOMPARE(api.isFilenameContentMixedAndSearchEnabled(), false); +} + +// ==================== ContentResultAPI Tests ==================== + +void tst_TextSearchAPI::contentResult_inheritance() +{ + SearchResult result("/test/path"); + ContentResultAPI api(result); + + // 验证继承自 TextSearchResultAPI + api.setHighlightedContent("content match"); + QCOMPARE(api.highlightedContent(), QString("content match")); + + api.setFilename("document.pdf"); + QCOMPARE(api.filename(), QString("document.pdf")); + + api.setIsHidden(true); + QCOMPARE(api.isHidden(), true); + + api.setModifyTimestamp(1700000000); + QCOMPARE(api.modifyTimestamp(), 1700000000); + + api.setBirthTimestamp(1600000000); + QCOMPARE(api.birthTimestamp(), 1600000000); +} + +// ==================== OcrTextOptionsAPI Tests ==================== + +void tst_TextSearchAPI::ocrTextOptions_inheritance() +{ + SearchOptions options; + OcrTextOptionsAPI api(options); + + // 验证继承自 TextSearchOptionsAPI + api.setMaxPreviewLength(250); + QCOMPARE(api.maxPreviewLength(), 250); + + api.setSearchResultHighlightEnabled(true); + QCOMPARE(api.isSearchResultHighlightEnabled(), true); + + api.setFullTextRetrievalEnabled(false); + QCOMPARE(api.isFullTextRetrievalEnabled(), false); +} + +void tst_TextSearchAPI::ocrTextOptions_defaultValues() +{ + SearchOptions options; + OcrTextOptionsAPI api(options); + + // 验证默认值 + QCOMPARE(api.maxPreviewLength(), 200); + QCOMPARE(api.isSearchResultHighlightEnabled(), false); + QCOMPARE(api.isFullTextRetrievalEnabled(), true); + QCOMPARE(api.isFilenameOcrContentMixedAndSearchEnabled(), false); +} + +void tst_TextSearchAPI::ocrTextOptions_setFilenameOcrContentMixedAndSearchEnabled() +{ + SearchOptions options; + OcrTextOptionsAPI api(options); + + QCOMPARE(api.isFilenameOcrContentMixedAndSearchEnabled(), false); + + api.setFilenameOcrContentMixedAndSearchEnabled(true); + QCOMPARE(api.isFilenameOcrContentMixedAndSearchEnabled(), true); + + api.setFilenameOcrContentMixedAndSearchEnabled(false); + QCOMPARE(api.isFilenameOcrContentMixedAndSearchEnabled(), false); +} + +// ==================== OcrTextResultAPI Tests ==================== + +void tst_TextSearchAPI::ocrTextResult_inheritance() +{ + SearchResult result("/test/path"); + OcrTextResultAPI api(result); + + // 验证继承自 TextSearchResultAPI + api.setHighlightedContent("OCR match"); + QCOMPARE(api.highlightedContent(), QString("OCR match")); + + api.setFilename("image.png"); + QCOMPARE(api.filename(), QString("image.png")); + + api.setIsHidden(true); + QCOMPARE(api.isHidden(), true); + + api.setModifyTimestamp(1700000000); + QCOMPARE(api.modifyTimestamp(), 1700000000); + + api.setBirthTimestamp(1600000000); + QCOMPARE(api.birthTimestamp(), 1600000000); +} + +void tst_TextSearchAPI::ocrTextResult_ocrContent() +{ + SearchResult result("/test/path"); + OcrTextResultAPI api(result); + + QVERIFY(api.ocrContent().isEmpty()); + + api.setOcrContent("This is extracted OCR text from image"); + QCOMPARE(api.ocrContent(), QString("This is extracted OCR text from image")); + + api.setOcrContent(""); + QVERIFY(api.ocrContent().isEmpty()); +} + +QObject *create_tst_TextSearchAPI() +{ + return new tst_TextSearchAPI(); +} + +#include "tst_textsearch_api.moc" diff --git a/include/dfm-search/dfm-search/contentsearchapi.h b/include/dfm-search/dfm-search/contentsearchapi.h index abb1414c..793f54a0 100644 --- a/include/dfm-search/dfm-search/contentsearchapi.h +++ b/include/dfm-search/dfm-search/contentsearchapi.h @@ -7,16 +7,16 @@ #include #include #include +#include DFM_SEARCH_BEGIN_NS /** * @brief The ContentOptionsAPI class provides content search specific options * - * This class extends the base SearchOptions with content search specific settings, - * such as file type filters and content preview length. + * This class extends TextSearchOptionsAPI with content search specific settings. */ -class ContentOptionsAPI +class ContentOptionsAPI : public TextSearchOptionsAPI { public: /** @@ -25,55 +25,6 @@ class ContentOptionsAPI */ explicit ContentOptionsAPI(SearchOptions &options); - /** - * @brief Set the maximum length for content preview - * @param length The maximum preview length in characters - */ - void setMaxPreviewLength(int length); - - /** - * @brief Get the maximum content preview length - * @return The maximum preview length in characters - */ - int maxPreviewLength() const; - - /** - * @brief Enables or disables HTML highlighting in search results. - * - * When enabled, matching keywords in search results will be wrapped in HTML tags - * (e.g., `keyword`) for visual highlighting. - * Note: Enabling this feature may incur additional processing overhead. - * - * @param enable Set to @c true to enable HTML highlighting, @c false to disable. - */ - void setSearchResultHighlightEnabled(bool enable); - - /** - * @brief Returns whether HTML highlighting in search results is enabled. - * - * @return @c true if search results will include HTML highlighting tags, - * @c false otherwise (plaintext results). - */ - bool isSearchResultHighlightEnabled() const; - - /** - * @brief Enables or disables full-text content retrieval in search results. - * - * When enable, search operations will return the complete file content along with metadata. - * This provides more detailed results but significantly increases memory usage and processing time. - * - * @param enable Set to @c true to retrieve full file contents, @c false to return metadata only. - */ - void setFullTextRetrievalEnabled(bool enable); - - /** - * @brief Checks if full-text content retrieval is enabled. - * - * @return @c true if search results will include complete file contents, - * @c false if only file metadata will be returned. - */ - bool isFullTextRetrievalEnabled() const; - /** * @brief Sets whether the extended AND search behavior across 'contents' and 'filename' fields is enabled. * @param enabled True to enable the feature, false to disable it. @@ -97,112 +48,23 @@ class ContentOptionsAPI * @return True if the filename-content mixed AND search is enabled, false otherwise. */ bool isFilenameContentMixedAndSearchEnabled() const; - -private: - SearchOptions &m_options; }; /** * @brief The ContentResultAPI class provides content search specific result handling * - * This class extends the base SearchResult with content search specific features, - * such as highlighted content preview. - * - * When detailed results are enabled, this API provides access to additional - * metadata including filename, hidden status, and time information. + * This class extends TextSearchResultAPI for content search results. */ -class ContentResultAPI +class ContentResultAPI : public TextSearchResultAPI { public: /** * @brief Constructor * @param result The SearchResult object to operate on */ - ContentResultAPI(SearchResult &result); - - // ==================== Content Attributes ==================== - - /** - * @brief Get the highlighted content preview - * @return The highlighted content as QString - */ - QString highlightedContent() const; - - /** - * @brief Set the highlighted content preview - * @param content The highlighted content to set - */ - void setHighlightedContent(const QString &content); - - // ==================== Extended Attributes ==================== - - /** - * @brief Get the file name (without path) - * @return The file name - */ - QString filename() const; - - /** - * @brief Set the file name - * @param name The file name to set - */ - void setFilename(const QString &name); - - /** - * @brief Check if the file is hidden - * @return true if the file is hidden, false otherwise - */ - bool isHidden() const; - - /** - * @brief Set whether the file is hidden - * @param hidden true if the file is hidden, false otherwise - */ - void setIsHidden(bool hidden); - - // ==================== Modification Time ==================== - - /** - * @brief Set the modification time timestamp - * @param timestamp Unix timestamp in seconds - */ - void setModifyTimestamp(qint64 timestamp); - - /** - * @brief Get the modification time timestamp - * @return Unix timestamp in seconds, 0 if not set - */ - qint64 modifyTimestamp() const; - - /** - * @brief Get the modification time as a formatted string - * @return Formatted time string (yyyy-MM-dd HH:mm:ss) - */ - QString modifyTimeString() const; - - // ==================== Birth/Creation Time ==================== - - /** - * @brief Set the birth/creation time timestamp - * @param timestamp Unix timestamp in seconds - */ - void setBirthTimestamp(qint64 timestamp); - - /** - * @brief Get the birth/creation time timestamp - * @return Unix timestamp in seconds, 0 if not set - */ - qint64 birthTimestamp() const; - - /** - * @brief Get the birth/creation time as a formatted string - * @return Formatted time string (yyyy-MM-dd HH:mm:ss) - */ - QString birthTimeString() const; - -private: - SearchResult &m_result; + explicit ContentResultAPI(SearchResult &result); }; + DFM_SEARCH_END_NS #endif // CONTENTSEARCHAPI_H diff --git a/include/dfm-search/dfm-search/ocrtextsearchapi.h b/include/dfm-search/dfm-search/ocrtextsearchapi.h index 4e854ee2..51ef0207 100644 --- a/include/dfm-search/dfm-search/ocrtextsearchapi.h +++ b/include/dfm-search/dfm-search/ocrtextsearchapi.h @@ -7,16 +7,16 @@ #include #include #include +#include DFM_SEARCH_BEGIN_NS /** * @brief The OcrTextOptionsAPI class provides OCR text search specific options * - * This class extends the base SearchOptions with OCR text search specific settings. - * OCR text search is a simplified version of content search without highlighting support. + * This class extends TextSearchOptionsAPI with OCR text search specific settings. */ -class OcrTextOptionsAPI +class OcrTextOptionsAPI : public TextSearchOptionsAPI { public: /** @@ -48,30 +48,21 @@ class OcrTextOptionsAPI * @return True if the filename-OCR content mixed AND search is enabled, false otherwise. */ bool isFilenameOcrContentMixedAndSearchEnabled() const; - -private: - SearchOptions &m_options; }; /** * @brief The OcrTextResultAPI class provides OCR text search specific result handling * - * This class extends the base SearchResult with OCR text search specific features. - * Note: OCR text search does not support content highlighting like content search. - * - * When detailed results are enabled, this API provides access to additional - * metadata including filename, hidden status, and time information. + * This class extends TextSearchResultAPI for OCR text search results. */ -class OcrTextResultAPI +class OcrTextResultAPI : public TextSearchResultAPI { public: /** * @brief Constructor * @param result The SearchResult object to operate on */ - OcrTextResultAPI(SearchResult &result); - - // ==================== OCR Content Attributes ==================== + explicit OcrTextResultAPI(SearchResult &result); /** * @brief Get the OCR extracted text content @@ -84,75 +75,6 @@ class OcrTextResultAPI * @param content The OCR extracted text to set */ void setOcrContent(const QString &content); - - // ==================== Extended Attributes ==================== - - /** - * @brief Get the file name (without path) - * @return The file name - */ - QString filename() const; - - /** - * @brief Set the file name - * @param name The file name to set - */ - void setFilename(const QString &name); - - /** - * @brief Check if the file is hidden - * @return true if the file is hidden, false otherwise - */ - bool isHidden() const; - - /** - * @brief Set whether the file is hidden - * @param hidden true if the file is hidden, false otherwise - */ - void setIsHidden(bool hidden); - - // ==================== Modification Time ==================== - - /** - * @brief Set the modification time timestamp - * @param timestamp Unix timestamp in seconds - */ - void setModifyTimestamp(qint64 timestamp); - - /** - * @brief Get the modification time timestamp - * @return Unix timestamp in seconds, 0 if not set - */ - qint64 modifyTimestamp() const; - - /** - * @brief Get the modification time as a formatted string - * @return Formatted time string (yyyy-MM-dd HH:mm:ss) - */ - QString modifyTimeString() const; - - // ==================== Birth/Creation Time ==================== - - /** - * @brief Set the birth/creation time timestamp - * @param timestamp Unix timestamp in seconds - */ - void setBirthTimestamp(qint64 timestamp); - - /** - * @brief Get the birth/creation time timestamp - * @return Unix timestamp in seconds, 0 if not set - */ - qint64 birthTimestamp() const; - - /** - * @brief Get the birth/creation time as a formatted string - * @return Formatted time string (yyyy-MM-dd HH:mm:ss) - */ - QString birthTimeString() const; - -private: - SearchResult &m_result; }; DFM_SEARCH_END_NS diff --git a/include/dfm-search/dfm-search/textsearchapi.h b/include/dfm-search/dfm-search/textsearchapi.h new file mode 100644 index 00000000..3f52e8be --- /dev/null +++ b/include/dfm-search/dfm-search/textsearchapi.h @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef TEXTSEARCHAPI_H +#define TEXTSEARCHAPI_H + +#include +#include +#include + +DFM_SEARCH_BEGIN_NS + +/** + * @brief The TextSearchOptionsAPI class provides common text search options + * + * This class serves as a base for content and OCR text search options, + * providing shared functionality for preview length, highlighting, and full-text retrieval. + */ +class TextSearchOptionsAPI +{ +public: + /** + * @brief Constructor + * @param options The SearchOptions object to operate on + */ + explicit TextSearchOptionsAPI(SearchOptions &options); + + // ==================== Preview and Highlight Settings ==================== + + /** + * @brief Sets the maximum length for content preview in search results. + * @param length The maximum preview length in characters. + */ + void setMaxPreviewLength(int length); + + /** + * @brief Gets the maximum length for content preview in search results. + * @return The maximum preview length in characters. + */ + int maxPreviewLength() const; + + /** + * @brief Enables or disables HTML highlighting in search results. + * + * When enabled, matching keywords in search results will be wrapped in HTML tags + * (e.g., `keyword`) for visual highlighting. + * Note: Enabling this feature may incur additional processing overhead. + * + * @param enable Set to @c true to enable HTML highlighting, @c false to disable. + */ + void setSearchResultHighlightEnabled(bool enable); + + /** + * @brief Returns whether HTML highlighting in search results is enabled. + * + * @return @c true if search results will include HTML highlighting tags, + * @c false otherwise (plaintext results). + */ + bool isSearchResultHighlightEnabled() const; + + /** + * @brief Enables or disables full-text content retrieval in search results. + * + * When enabled, search operations will return the complete text content along with metadata. + * This provides more detailed results but significantly increases memory usage and processing time. + * + * @param enable Set to @c true to retrieve full text contents, @c false to return metadata only. + */ + void setFullTextRetrievalEnabled(bool enable); + + /** + * @brief Returns whether full-text content retrieval is enabled. + * + * @return @c true if full-text content retrieval is enabled, @c false otherwise. + */ + bool isFullTextRetrievalEnabled() const; + +protected: + SearchOptions &m_options; +}; + +/** + * @brief The TextSearchResultAPI class provides common text search result handling + * + * This class serves as a base for content and OCR text search results, + * providing shared functionality for highlighted content and file metadata. + */ +class TextSearchResultAPI +{ +public: + /** + * @brief Constructor + * @param result The SearchResult object to operate on + */ + explicit TextSearchResultAPI(SearchResult &result); + + // ==================== Highlighted Content ==================== + + /** + * @brief Get the highlighted content preview + * @return The highlighted content as QString + */ + QString highlightedContent() const; + + /** + * @brief Set the highlighted content preview + * @param content The highlighted content to set + */ + void setHighlightedContent(const QString &content); + + // ==================== Extended Attributes ==================== + + /** + * @brief Get the file name (without path) + * @return The file name + */ + QString filename() const; + + /** + * @brief Set the file name + * @param name The file name to set + */ + void setFilename(const QString &name); + + /** + * @brief Check if the file is hidden + * @return true if the file is hidden, false otherwise + */ + bool isHidden() const; + + /** + * @brief Set whether the file is hidden + * @param hidden true if the file is hidden, false otherwise + */ + void setIsHidden(bool hidden); + + // ==================== Modification Time ==================== + + /** + * @brief Set the modification time timestamp + * @param timestamp Unix timestamp in seconds + */ + void setModifyTimestamp(qint64 timestamp); + + /** + * @brief Get the modification time timestamp + * @return Unix timestamp in seconds, 0 if not set + */ + qint64 modifyTimestamp() const; + + /** + * @brief Get the modification time as a formatted string + * @return Formatted time string (yyyy-MM-dd HH:mm:ss) + */ + QString modifyTimeString() const; + + // ==================== Birth/Creation Time ==================== + + /** + * @brief Set the birth/creation time timestamp + * @param timestamp Unix timestamp in seconds + */ + void setBirthTimestamp(qint64 timestamp); + + /** + * @brief Get the birth/creation time timestamp + * @return Unix timestamp in seconds, 0 if not set + */ + qint64 birthTimestamp() const; + + /** + * @brief Get the birth/creation time as a formatted string + * @return Formatted time string (yyyy-MM-dd HH:mm:ss) + */ + QString birthTimeString() const; + +protected: + SearchResult &m_result; +}; + +DFM_SEARCH_END_NS + +#endif // TEXTSEARCHAPI_H diff --git a/src/dfm-search/dfm-search-client/README.md b/src/dfm-search/dfm-search-client/README.md index 6b8af335..2f8d976f 100644 --- a/src/dfm-search/dfm-search-client/README.md +++ b/src/dfm-search/dfm-search-client/README.md @@ -18,6 +18,11 @@ A comprehensive file search utility for Deepin File Manager that provides high-p - Result highlighting - Preview of matching content +- **OCR search**: Search text extracted from images via OCR: + - Search text in image files (PNG, JPG, etc.) + - Result highlighting + - Preview of matching OCR text + ## Search Methods - **Indexed search**: Fast search using pre-built Lucene indexes @@ -33,7 +38,7 @@ dfm6-search-client [options] ### Options -- `--type=`: Search type (default: filename) +- `--type=`: Search type (default: filename) - `--method=`: Search method (default: indexed) - `--query=`: Query type (default: simple) - `--case-sensitive`: Enable case sensitivity @@ -41,8 +46,8 @@ dfm6-search-client [options] - `--pinyin`: Enable pinyin search (for filename search) - `--file-types=`: Filter by file types, comma separated - `--file-extensions=`: Filter by file extensions, comma separated -- `--max-results=`: Maximum number of results -- `--max-preview=`: Max content preview length (for content search) +- `--max-results=`: Maximum number of results (0 for unlimited, default: 0) +- `--max-preview=`: Max content preview length (for content/ocr search) ### Examples diff --git a/src/dfm-search/dfm-search-client/cli_options.cpp b/src/dfm-search/dfm-search-client/cli_options.cpp index 0327cb3c..63986e61 100644 --- a/src/dfm-search/dfm-search-client/cli_options.cpp +++ b/src/dfm-search/dfm-search-client/cli_options.cpp @@ -23,7 +23,7 @@ CliOptions::CliOptions() m_pinyinAcronymOption(QStringList() << "pinyin-acronym", "Enable pinyin acronym search (for filename search)"), m_fileTypesOption(QStringList() << "file-types", "Filter by file types, comma separated", "types"), m_fileExtensionsOption(QStringList() << "file-extensions", "Filter by file extensions, comma separated", "extensions"), - m_maxResultsOption(QStringList() << "max-results", "Maximum number of results", "number", "100"), + m_maxResultsOption(QStringList() << "max-results", "Maximum number of results (0 for unlimited)", "number", "0"), m_maxPreviewOption(QStringList() << "max-preview", "Max content preview length", "length", "200"), m_wildcardOption(QStringList() << "wildcard", "Enable wildcard search with * and ? patterns"), m_jsonOption(QStringList() << "json" @@ -106,8 +106,8 @@ void CliOptions::printHelp() const std::cout << " --pinyin-acronym Enable pinyin acronym search (for filename search)" << std::endl; std::cout << " --file-types= Filter by file types, comma separated" << std::endl; std::cout << " --file-extensions= Filter by file extensions, comma separated" << std::endl; - std::cout << " --max-results= Maximum number of results" << std::endl; - std::cout << " --max-preview= Max content preview length (for content search)" << std::endl; + std::cout << " --max-results= Maximum number of results (0 for unlimited)" << std::endl; + std::cout << " --max-preview= Max content preview length (for content/ocr search)" << std::endl; std::cout << std::endl; std::cout << "Time Range Filter Options:" << std::endl; std::cout << " --time-field= Time field to filter (birth=creation, modify=modification)" << std::endl; @@ -224,7 +224,7 @@ bool CliOptions::parse(QCoreApplication &app, SearchCliConfig &config) if (m_parser.isSet(m_maxResultsOption)) { bool ok; int maxResults = m_parser.value(m_maxResultsOption).toInt(&ok); - if (ok && maxResults > 0) { + if (ok && maxResults >= 0) { config.maxResults = maxResults; } } diff --git a/src/dfm-search/dfm-search-client/cli_options.h b/src/dfm-search/dfm-search-client/cli_options.h index f93a6817..10bdbf15 100644 --- a/src/dfm-search/dfm-search-client/cli_options.h +++ b/src/dfm-search/dfm-search-client/cli_options.h @@ -41,7 +41,7 @@ struct SearchCliConfig // 过滤选项 QStringList fileTypes; QStringList fileExtensions; - int maxResults = 100; + int maxResults = 0; // 0 表示不限制 int maxPreviewLength = 200; // 时间范围过滤 diff --git a/src/dfm-search/dfm-search-client/main.cpp b/src/dfm-search/dfm-search-client/main.cpp index 11241f88..26ed2457 100644 --- a/src/dfm-search/dfm-search-client/main.cpp +++ b/src/dfm-search/dfm-search-client/main.cpp @@ -55,6 +55,9 @@ static void configureSearchOptions(SearchOptions &options, const SearchCliConfig contentOptions.setFilenameContentMixedAndSearchEnabled(true); } else if (config.searchType == SearchType::Ocr) { OcrTextOptionsAPI ocrTextOptions(options); + ocrTextOptions.setMaxPreviewLength(config.maxPreviewLength); + ocrTextOptions.setFullTextRetrievalEnabled(true); + ocrTextOptions.setSearchResultHighlightEnabled(true); ocrTextOptions.setFilenameOcrContentMixedAndSearchEnabled(true); } diff --git a/src/dfm-search/dfm-search-client/output/json_output.cpp b/src/dfm-search/dfm-search-client/output/json_output.cpp index f1811862..b6208a2d 100644 --- a/src/dfm-search/dfm-search-client/output/json_output.cpp +++ b/src/dfm-search/dfm-search-client/output/json_output.cpp @@ -114,6 +114,8 @@ QJsonValue JsonOutput::resultToJson(const SearchResult &result) OcrTextResultAPI resultAPI(const_cast(result)); + obj["contentMatch"] = resultAPI.highlightedContent(); + QString ocrContent = resultAPI.ocrContent(); if (!ocrContent.isEmpty()) { obj["ocrContent"] = ocrContent; diff --git a/src/dfm-search/dfm-search-client/output/text_output.cpp b/src/dfm-search/dfm-search-client/output/text_output.cpp index 15b0bd87..c1cca72e 100644 --- a/src/dfm-search/dfm-search-client/output/text_output.cpp +++ b/src/dfm-search/dfm-search-client/output/text_output.cpp @@ -134,6 +134,8 @@ void TextOutput::printSearchResult(const SearchResult &result) } else if (m_searchType == SearchType::Ocr) { OcrTextResultAPI resultAPI(const_cast(result)); + std::cout << " Content match: " << resultAPI.highlightedContent().toStdString() << std::endl; + QString ocrContent = resultAPI.ocrContent(); if (!ocrContent.isEmpty()) { std::cout << " OCR content: " << ocrContent.toStdString() << std::endl; diff --git a/src/dfm-search/dfm-search-lib/contentsearch/contentsearchapi.cpp b/src/dfm-search/dfm-search-lib/contentsearch/contentsearchapi.cpp index 913df0ca..8e8d340f 100644 --- a/src/dfm-search/dfm-search-lib/contentsearch/contentsearchapi.cpp +++ b/src/dfm-search/dfm-search-lib/contentsearch/contentsearchapi.cpp @@ -2,54 +2,21 @@ // // SPDX-License-Identifier: GPL-3.0-or-later #include -#include - -#include DFM_SEARCH_BEGIN_NS ContentOptionsAPI::ContentOptionsAPI(SearchOptions &options) - : m_options(options) + : TextSearchOptionsAPI(options) { // init default if (!m_options.hasCustomOption("maxPreviewLength")) - setMaxPreviewLength(50); - if (!m_options.hasCustomOption("searchResultHighligh")) + setMaxPreviewLength(200); + if (!m_options.hasCustomOption("searchResultHighlight")) setSearchResultHighlightEnabled(false); if (!m_options.hasCustomOption("fullTextRetrieval")) setFullTextRetrievalEnabled(true); } -void ContentOptionsAPI::setMaxPreviewLength(int length) -{ - m_options.setCustomOption("maxPreviewLength", length); -} - -int ContentOptionsAPI::maxPreviewLength() const -{ - return m_options.customOption("maxPreviewLength").toInt(); -} - -void ContentOptionsAPI::setSearchResultHighlightEnabled(bool enable) -{ - m_options.setCustomOption("searchResultHighligh", enable); -} - -bool ContentOptionsAPI::isSearchResultHighlightEnabled() const -{ - return m_options.customOption("searchResultHighligh").toBool(); -} - -void ContentOptionsAPI::setFullTextRetrievalEnabled(bool enable) -{ - m_options.setCustomOption("fullTextRetrieval", enable); -} - -bool ContentOptionsAPI::isFullTextRetrievalEnabled() const -{ - return m_options.customOption("fullTextRetrieval").toBool(); -} - void ContentOptionsAPI::setFilenameContentMixedAndSearchEnabled(bool enabled) { m_options.setCustomOption("filenameContentMixedAndSearchEnabled", enabled); @@ -61,76 +28,8 @@ bool ContentOptionsAPI::isFilenameContentMixedAndSearchEnabled() const } ContentResultAPI::ContentResultAPI(SearchResult &result) - : m_result(result) -{ -} - -QString ContentResultAPI::highlightedContent() const -{ - return m_result.customAttribute("highlightedContent").toString(); -} - -void ContentResultAPI::setHighlightedContent(const QString &content) -{ - m_result.setCustomAttribute("highlightedContent", content); -} - -// ==================== Extended Attributes ==================== - -QString ContentResultAPI::filename() const -{ - return m_result.customAttribute("filename").toString(); -} - -void ContentResultAPI::setFilename(const QString &name) -{ - m_result.setCustomAttribute("filename", name); -} - -bool ContentResultAPI::isHidden() const -{ - return m_result.customAttribute("isHidden").toBool(); -} - -void ContentResultAPI::setIsHidden(bool hidden) -{ - m_result.setCustomAttribute("isHidden", hidden); -} - -// ==================== Modification Time ==================== - -void ContentResultAPI::setModifyTimestamp(qint64 timestamp) -{ - m_result.setCustomAttribute("modifyTimestamp", timestamp); -} - -qint64 ContentResultAPI::modifyTimestamp() const -{ - return m_result.customAttribute("modifyTimestamp").toLongLong(); -} - -QString ContentResultAPI::modifyTimeString() const -{ - qint64 ts = modifyTimestamp(); - return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); -} - -// ==================== Birth/Creation Time ==================== - -void ContentResultAPI::setBirthTimestamp(qint64 timestamp) -{ - m_result.setCustomAttribute("birthTimestamp", timestamp); -} - -qint64 ContentResultAPI::birthTimestamp() const -{ - return m_result.customAttribute("birthTimestamp").toLongLong(); -} - -QString ContentResultAPI::birthTimeString() const + : TextSearchResultAPI(result) { - qint64 ts = birthTimestamp(); - return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); } DFM_SEARCH_END_NS diff --git a/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextsearchapi.cpp b/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextsearchapi.cpp index d7d1b06c..30e3f8c8 100644 --- a/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextsearchapi.cpp +++ b/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextsearchapi.cpp @@ -2,16 +2,19 @@ // // SPDX-License-Identifier: GPL-3.0-or-later #include -#include - -#include DFM_SEARCH_BEGIN_NS OcrTextOptionsAPI::OcrTextOptionsAPI(SearchOptions &options) - : m_options(options) + : TextSearchOptionsAPI(options) { // init default + if (!m_options.hasCustomOption("maxPreviewLength")) + setMaxPreviewLength(200); + if (!m_options.hasCustomOption("searchResultHighlight")) + setSearchResultHighlightEnabled(false); + if (!m_options.hasCustomOption("fullTextRetrieval")) + setFullTextRetrievalEnabled(true); if (!m_options.hasCustomOption("filenameOcrContentMixedAndSearchEnabled")) setFilenameOcrContentMixedAndSearchEnabled(false); } @@ -27,7 +30,7 @@ bool OcrTextOptionsAPI::isFilenameOcrContentMixedAndSearchEnabled() const } OcrTextResultAPI::OcrTextResultAPI(SearchResult &result) - : m_result(result) + : TextSearchResultAPI(result) { } @@ -41,62 +44,4 @@ void OcrTextResultAPI::setOcrContent(const QString &content) m_result.setCustomAttribute("ocrContent", content); } -// ==================== Extended Attributes ==================== - -QString OcrTextResultAPI::filename() const -{ - return m_result.customAttribute("filename").toString(); -} - -void OcrTextResultAPI::setFilename(const QString &name) -{ - m_result.setCustomAttribute("filename", name); -} - -bool OcrTextResultAPI::isHidden() const -{ - return m_result.customAttribute("isHidden").toBool(); -} - -void OcrTextResultAPI::setIsHidden(bool hidden) -{ - m_result.setCustomAttribute("isHidden", hidden); -} - -// ==================== Modification Time ==================== - -void OcrTextResultAPI::setModifyTimestamp(qint64 timestamp) -{ - m_result.setCustomAttribute("modifyTimestamp", timestamp); -} - -qint64 OcrTextResultAPI::modifyTimestamp() const -{ - return m_result.customAttribute("modifyTimestamp").toLongLong(); -} - -QString OcrTextResultAPI::modifyTimeString() const -{ - qint64 ts = modifyTimestamp(); - return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); -} - -// ==================== Birth/Creation Time ==================== - -void OcrTextResultAPI::setBirthTimestamp(qint64 timestamp) -{ - m_result.setCustomAttribute("birthTimestamp", timestamp); -} - -qint64 OcrTextResultAPI::birthTimestamp() const -{ - return m_result.customAttribute("birthTimestamp").toLongLong(); -} - -QString OcrTextResultAPI::birthTimeString() const -{ - qint64 ts = birthTimestamp(); - return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); -} - DFM_SEARCH_END_NS 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 0a496d77..e561b2a6 100644 --- a/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp +++ b/src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp @@ -20,6 +20,7 @@ #include "3rdparty/fulltext/chineseanalyzer.h" #include "utils/cancellablecollector.h" +#include "utils/contenthighlighter.h" #include "utils/lucenequeryutils.h" #include "utils/searchutility.h" #include "utils/lucene_cancellation_compat.h" @@ -245,6 +246,11 @@ void OcrTextIndexedStrategy::processSearchResults(const Lucene::IndexSearcherPtr const QStringList &searchExcludedPaths = m_options.searchExcludedPaths(); auto docsSize = scoreDocs.size(); + OcrTextOptionsAPI optAPI(m_options); + bool enableHTML = optAPI.isSearchResultHighlightEnabled(); + int previewLen = optAPI.maxPreviewLength() > 0 ? optAPI.maxPreviewLength() : 50; + bool enableRetrieval = optAPI.isFullTextRetrievalEnabled(); + for (int32_t i = 0; i < docsSize; ++i) { if (m_cancelled.load()) { qInfo() << "OCR text search cancelled"; @@ -320,16 +326,34 @@ void OcrTextIndexedStrategy::processSearchResults(const Lucene::IndexSearcherPtr // Create search result SearchResult result(path); - // 设置详细结果(如果启用) - if (Q_UNLIKELY(m_options.detailedResultsEnabled())) { - OcrTextResultAPI resultApi(result); + // 设置 OCR 内容结果 + OcrTextResultAPI resultApi(result); - // OCR 内容 - Lucene::String ocrContentField = doc->get(LuceneFieldNames::OcrText::kOcrContents); - if (!ocrContentField.empty()) { - resultApi.setOcrContent(QString::fromStdWString(ocrContentField)); + // 使用ContentHighlighter命名空间进行高亮 + if (enableRetrieval) { + try { + // Safely get OCR contents with null check + Lucene::String ocrContentField = doc->get(LuceneFieldNames::OcrText::kOcrContents); + if (!ocrContentField.empty()) { + const QString content = QString::fromStdWString(ocrContentField); + // 设置原始 OCR 内容 + resultApi.setOcrContent(content); + // 设置高亮内容 + const QString highlightedContent = ContentHighlighter::customHighlight( + m_keywords, content, previewLen, enableHTML); + resultApi.setHighlightedContent(highlightedContent); + } + } catch (const Lucene::LuceneException &e) { + qWarning() << "Exception retrieving OCR content field:" << QString::fromStdWString(e.getError()); + // Continue without content highlight + } catch (const std::exception &e) { + qWarning() << "Standard exception retrieving OCR content field:" << e.what(); + // Continue without content highlight } + } + // 设置详细结果(如果启用) + if (Q_UNLIKELY(m_options.detailedResultsEnabled())) { // 文件名 Lucene::String filenameField = doc->get(LuceneFieldNames::OcrText::kFilename); if (!filenameField.empty()) { diff --git a/src/dfm-search/dfm-search-lib/textsearch/textsearchapi.cpp b/src/dfm-search/dfm-search-lib/textsearch/textsearchapi.cpp new file mode 100644 index 00000000..e2182d9f --- /dev/null +++ b/src/dfm-search/dfm-search-lib/textsearch/textsearchapi.cpp @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include + +#include + +DFM_SEARCH_BEGIN_NS + +// ==================== TextSearchOptionsAPI ==================== + +TextSearchOptionsAPI::TextSearchOptionsAPI(SearchOptions &options) + : m_options(options) +{ +} + +void TextSearchOptionsAPI::setMaxPreviewLength(int length) +{ + m_options.setCustomOption("maxPreviewLength", length); +} + +int TextSearchOptionsAPI::maxPreviewLength() const +{ + return m_options.customOption("maxPreviewLength").toInt(); +} + +void TextSearchOptionsAPI::setSearchResultHighlightEnabled(bool enable) +{ + m_options.setCustomOption("searchResultHighlight", enable); +} + +bool TextSearchOptionsAPI::isSearchResultHighlightEnabled() const +{ + return m_options.customOption("searchResultHighlight").toBool(); +} + +void TextSearchOptionsAPI::setFullTextRetrievalEnabled(bool enable) +{ + m_options.setCustomOption("fullTextRetrieval", enable); +} + +bool TextSearchOptionsAPI::isFullTextRetrievalEnabled() const +{ + return m_options.customOption("fullTextRetrieval").toBool(); +} + +// ==================== TextSearchResultAPI ==================== + +TextSearchResultAPI::TextSearchResultAPI(SearchResult &result) + : m_result(result) +{ +} + +QString TextSearchResultAPI::highlightedContent() const +{ + return m_result.customAttribute("highlightedContent").toString(); +} + +void TextSearchResultAPI::setHighlightedContent(const QString &content) +{ + m_result.setCustomAttribute("highlightedContent", content); +} + +QString TextSearchResultAPI::filename() const +{ + return m_result.customAttribute("filename").toString(); +} + +void TextSearchResultAPI::setFilename(const QString &name) +{ + m_result.setCustomAttribute("filename", name); +} + +bool TextSearchResultAPI::isHidden() const +{ + return m_result.customAttribute("isHidden").toBool(); +} + +void TextSearchResultAPI::setIsHidden(bool hidden) +{ + m_result.setCustomAttribute("isHidden", hidden); +} + +void TextSearchResultAPI::setModifyTimestamp(qint64 timestamp) +{ + m_result.setCustomAttribute("modifyTimestamp", timestamp); +} + +qint64 TextSearchResultAPI::modifyTimestamp() const +{ + return m_result.customAttribute("modifyTimestamp").toLongLong(); +} + +QString TextSearchResultAPI::modifyTimeString() const +{ + qint64 ts = modifyTimestamp(); + return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); +} + +void TextSearchResultAPI::setBirthTimestamp(qint64 timestamp) +{ + m_result.setCustomAttribute("birthTimestamp", timestamp); +} + +qint64 TextSearchResultAPI::birthTimestamp() const +{ + return m_result.customAttribute("birthTimestamp").toLongLong(); +} + +QString TextSearchResultAPI::birthTimeString() const +{ + qint64 ts = birthTimestamp(); + return ts > 0 ? TimeResultAPI::formatTimestamp(ts) : QString(); +} + +DFM_SEARCH_END_NS