Add group search

* Allow searching by group using the `group:` field.
* Group hierarchies can be searched by including a '/' in the search term.
This commit is contained in:
BO41 2020-05-08 11:13:15 -04:00 committed by Jonathan White
parent 485852c9db
commit f1080d633e
5 changed files with 82 additions and 6 deletions

View File

@ -54,7 +54,6 @@ QList<Entry*> EntrySearcher::search(const QList<SearchTerm>& searchTerms, const
QList<Entry*> EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch) QList<Entry*> EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch)
{ {
Q_ASSERT(baseGroup); Q_ASSERT(baseGroup);
parseSearchTerms(searchString); parseSearchTerms(searchString);
return repeat(baseGroup, forceSearch); return repeat(baseGroup, forceSearch);
} }
@ -73,7 +72,7 @@ QList<Entry*> EntrySearcher::repeat(const Group* baseGroup, bool forceSearch)
QList<Entry*> results; QList<Entry*> results;
for (const auto group : baseGroup->groupsRecursive(true)) { for (const auto group : baseGroup->groupsRecursive(true)) {
if (forceSearch || group->resolveSearchingEnabled()) { if (forceSearch || group->resolveSearchingEnabled()) {
for (auto* entry : group->entries()) { for (const auto entry : group->entries()) {
if (searchEntryImpl(entry)) { if (searchEntryImpl(entry)) {
results.append(entry); results.append(entry);
} }
@ -142,12 +141,14 @@ bool EntrySearcher::isCaseSensitive()
return m_caseSensitive; return m_caseSensitive;
} }
bool EntrySearcher::searchEntryImpl(Entry* entry) bool EntrySearcher::searchEntryImpl(const Entry* entry)
{ {
// Pre-load in case they are needed // Pre-load in case they are needed
auto attributes_keys = entry->attributes()->customKeys(); auto attributes_keys = entry->attributes()->customKeys();
auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys)); auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys));
auto attachments = QStringList(entry->attachments()->keys()); auto attachments = QStringList(entry->attachments()->keys());
// Build a group hierarchy to allow searching for e.g. /group1/subgroup*
auto hierarchy = entry->group()->hierarchy().join('/').prepend("/");
bool found; bool found;
for (const auto& term : m_searchTerms) { for (const auto& term : m_searchTerms) {
@ -181,6 +182,14 @@ bool EntrySearcher::searchEntryImpl(Entry* entry)
found = entry->attributes()->contains(term.word) found = entry->attributes()->contains(term.word)
&& term.regex.match(entry->attributes()->value(term.word)).hasMatch(); && term.regex.match(entry->attributes()->value(term.word)).hasMatch();
break; break;
case Field::Group:
// Match against the full hierarchy if the word contains a '/' otherwise just the group name
if (term.word.contains('/')) {
found = term.regex.match(hierarchy).hasMatch();
} else {
found = term.regex.match(entry->group()->name()).hasMatch();
}
break;
default: default:
// Terms without a specific field try to match title, username, url, and notes // Terms without a specific field try to match title, username, url, and notes
found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch() found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch()
@ -209,7 +218,8 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
{QStringLiteral("title"), Field::Title}, {QStringLiteral("title"), Field::Title},
{QStringLiteral("u"), Field::Username}, // u: stands for username rather than url {QStringLiteral("u"), Field::Username}, // u: stands for username rather than url
{QStringLiteral("url"), Field::Url}, {QStringLiteral("url"), Field::Url},
{QStringLiteral("username"), Field::Username}}; {QStringLiteral("username"), Field::Username},
{QStringLiteral("group"), Field::Group}};
m_searchTerms.clear(); m_searchTerms.clear();
auto results = m_termParser.globalMatch(searchString); auto results = m_termParser.globalMatch(searchString);

View File

@ -38,7 +38,8 @@ public:
Notes, Notes,
AttributeKV, AttributeKV,
Attachment, Attachment,
AttributeValue AttributeValue,
Group
}; };
struct SearchTerm struct SearchTerm
@ -64,7 +65,7 @@ public:
bool isCaseSensitive(); bool isCaseSensitive();
private: private:
bool searchEntryImpl(Entry* entry); bool searchEntryImpl(const Entry* entry);
void parseSearchTerms(const QString& searchString); void parseSearchTerms(const QString& searchString);
bool m_caseSensitive; bool m_caseSensitive;

View File

@ -235,6 +235,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="QLabel" name="label_16">
<property name="text">
<string notr="true">group (g)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -262,3 +262,60 @@ void TestEntrySearcher::testCustomAttributesAreSearched()
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup); m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
QCOMPARE(m_searchResult.count(), 2); QCOMPARE(m_searchResult.count(), 2);
} }
void TestEntrySearcher::testGroup()
{
/**
* Root
* - group1 (1 entry)
* - subgroup1 (2 entries)
* - group2
* - subgroup2 (1 entry)
*/
Group* group1 = new Group();
Group* group2 = new Group();
group1->setParent(m_rootGroup);
group1->setName("group1");
group2->setParent(m_rootGroup);
group2->setName("group2");
Group* subgroup1 = new Group();
subgroup1->setName("subgroup1");
subgroup1->setParent(group1);
Group* subgroup2 = new Group();
subgroup2->setName("subgroup2");
subgroup2->setParent(group2);
Entry* eGroup1 = new Entry();
eGroup1->setTitle("Entry Group 1");
eGroup1->setGroup(group1);
Entry* eSub1 = new Entry();
eSub1->setTitle("test search term test");
eSub1->setGroup(subgroup1);
Entry* eSub2 = new Entry();
eSub2->setNotes("test test");
eSub2->setGroup(subgroup1);
Entry* eSub3 = new Entry();
eSub3->setNotes("test term test");
eSub3->setGroup(subgroup2);
m_searchResult = m_entrySearcher.search("group:subgroup", m_rootGroup);
QCOMPARE(m_searchResult.count(), 3);
m_searchResult = m_entrySearcher.search("g:subgroup1", m_rootGroup);
QCOMPARE(m_searchResult.count(), 2);
m_searchResult = m_entrySearcher.search("g:subgroup1 search", m_rootGroup);
QCOMPARE(m_searchResult.count(), 1);
m_searchResult = m_entrySearcher.search("g:*1/sub*1", m_rootGroup);
QCOMPARE(m_searchResult.count(), 2);
m_searchResult = m_entrySearcher.search("g:/group1 search", m_rootGroup);
QCOMPARE(m_searchResult.count(), 1);
}

View File

@ -36,6 +36,7 @@ private slots:
void testAllAttributesAreSearched(); void testAllAttributesAreSearched();
void testSearchTermParser(); void testSearchTermParser();
void testCustomAttributesAreSearched(); void testCustomAttributesAreSearched();
void testGroup();
private: private:
Group* m_rootGroup; Group* m_rootGroup;