Use SuggestionSearch instead of Search for better search functionality.

* Since `Search` is not compatible with those zim files which does not have Xapian index but `SuggestionSearch` have this functionality to search inside those zim files so we have used this.
* Update test cases for test new search functionality.
This commit is contained in:
MohitMali 2023-08-08 16:15:14 +05:30
parent 6235ba612a
commit c4dbe478b8
10 changed files with 34 additions and 37 deletions

View File

@ -37,9 +37,8 @@ import org.kiwix.libkiwix.JNIKiwixException
import org.kiwix.libzim.Archive import org.kiwix.libzim.Archive
import org.kiwix.libzim.DirectAccessInfo import org.kiwix.libzim.DirectAccessInfo
import org.kiwix.libzim.Item import org.kiwix.libzim.Item
import org.kiwix.libzim.Query import org.kiwix.libzim.SuggestionSearch
import org.kiwix.libzim.Search import org.kiwix.libzim.SuggestionSearcher
import org.kiwix.libzim.Searcher
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.IOException import java.io.IOException
@ -56,7 +55,7 @@ class ZimFileReader constructor(
val zimFile: File, val zimFile: File,
private val jniKiwixReader: Archive, private val jniKiwixReader: Archive,
private val nightModeConfig: NightModeConfig, private val nightModeConfig: NightModeConfig,
private val searcher: Searcher = Searcher(jniKiwixReader) private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
) { ) {
interface Factory { interface Factory {
fun create(file: File): ZimFileReader? fun create(file: File): ZimFileReader?
@ -130,9 +129,9 @@ class ZimFileReader constructor(
null null
} }
fun searchSuggestions(prefix: String): Search? = fun searchSuggestions(prefix: String): SuggestionSearch? =
try { try {
searcher.search(Query(prefix)) searcher.suggest(prefix)
} catch (ignore: Exception) { } catch (ignore: Exception) {
// to handled the exception if there is no FT Xapian index found in the current zim file // to handled the exception if there is no FT Xapian index found in the current zim file
null null

View File

@ -22,14 +22,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.libzim.Search import org.kiwix.libzim.SuggestionSearch
import javax.inject.Inject import javax.inject.Inject
interface SearchResultGenerator { interface SearchResultGenerator {
suspend fun generateSearchResults( suspend fun generateSearchResults(
searchTerm: String, searchTerm: String,
zimFileReader: ZimFileReader? zimFileReader: ZimFileReader?
): Search? ): SuggestionSearch?
} }
class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator { class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator {

View File

@ -30,7 +30,7 @@ data class SearchState(
if (searchTerm.isEmpty()) { if (searchTerm.isEmpty()) {
recentResults recentResults
} else { } else {
searchResultsWithTerm.search?.let { searchResultsWithTerm.suggestionSearch?.let {
val maximumResults = it.estimatedMatches val maximumResults = it.estimatedMatches
val safeEndIndex = val safeEndIndex =
if (startIndex + 100 < maximumResults) startIndex + 100 else maximumResults if (startIndex + 100 < maximumResults) startIndex + 100 else maximumResults

View File

@ -59,7 +59,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScree
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
import org.kiwix.libzim.Search import org.kiwix.libzim.SuggestionSearch
import javax.inject.Inject import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@ -174,4 +174,4 @@ class SearchViewModel @Inject constructor(
} }
} }
data class SearchResultsWithTerm(val searchTerm: String, val search: Search?) data class SearchResultsWithTerm(val searchTerm: String, val suggestionSearch: SuggestionSearch?)

View File

@ -30,11 +30,11 @@ internal class SearchStateTest {
@Test @Test
internal fun `visibleResults use searchResults when searchTerm is not empty`() { internal fun `visibleResults use searchResults when searchTerm is not empty`() {
val searchTerm = "notEmpty" val searchTerm = "notEmpty"
val searchWrapper: SearchWrapper = mockk() val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
val searchIteratorWrapper: SearchIteratorWrapper = mockk() val searchIteratorWrapper: SuggestionIteratorWrapper = mockk()
val entryWrapper: EntryWrapper = mockk() val entryWrapper: SuggestionItemWrapper = mockk()
val estimatedMatches = 1 val estimatedMatches = 1
every { searchWrapper.estimatedMatches } returns estimatedMatches.toLong() every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong()
// Settings list to hasNext() to ensure it returns true only for the first call. // Settings list to hasNext() to ensure it returns true only for the first call.
// Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value, // Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value,
// causing our test case to get stuck in an infinite loop due to this explicit setting. // causing our test case to get stuck in an infinite loop due to this explicit setting.
@ -42,7 +42,7 @@ internal class SearchStateTest {
every { searchIteratorWrapper.next() } returns entryWrapper every { searchIteratorWrapper.next() } returns entryWrapper
every { entryWrapper.title } returns searchTerm every { entryWrapper.title } returns searchTerm
every { every {
searchWrapper.getResults( suggestionSearchWrapper.getResults(
0, 0,
estimatedMatches estimatedMatches
) )
@ -50,7 +50,7 @@ internal class SearchStateTest {
assertThat( assertThat(
SearchState( SearchState(
searchTerm, searchTerm,
SearchResultsWithTerm("", searchWrapper), SearchResultsWithTerm("", suggestionSearchWrapper),
emptyList(), emptyList(),
FromWebView FromWebView
).getVisibleResults(0) ).getVisibleResults(0)

View File

@ -74,7 +74,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScree
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
import org.kiwix.libzim.Search import org.kiwix.libzim.SuggestionSearch
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
internal class SearchViewModelTest { internal class SearchViewModelTest {
@ -122,12 +122,12 @@ internal class SearchViewModelTest {
val item = ZimSearchResultListItem("") val item = ZimSearchResultListItem("")
val searchTerm = "searchTerm" val searchTerm = "searchTerm"
val searchOrigin = FromWebView val searchOrigin = FromWebView
val search: Search = mockk() val suggestionSearch: SuggestionSearch = mockk()
viewModel.state.test(this) viewModel.state.test(this)
.also { .also {
emissionOf( emissionOf(
searchTerm = searchTerm, searchTerm = searchTerm,
search = search, suggestionSearch = suggestionSearch,
databaseResults = listOf(RecentSearchListItem("")), databaseResults = listOf(RecentSearchListItem("")),
searchOrigin = searchOrigin searchOrigin = searchOrigin
) )
@ -135,7 +135,7 @@ internal class SearchViewModelTest {
.assertValue( .assertValue(
SearchState( SearchState(
searchTerm, searchTerm,
SearchResultsWithTerm(searchTerm, search), SearchResultsWithTerm(searchTerm, suggestionSearch),
listOf(RecentSearchListItem("")), listOf(RecentSearchListItem("")),
searchOrigin searchOrigin
) )
@ -243,14 +243,14 @@ internal class SearchViewModelTest {
private fun TestScope.emissionOf( private fun TestScope.emissionOf(
searchTerm: String, searchTerm: String,
search: Search, suggestionSearch: SuggestionSearch,
databaseResults: List<RecentSearchListItem>, databaseResults: List<RecentSearchListItem>,
searchOrigin: SearchOrigin searchOrigin: SearchOrigin
) { ) {
coEvery { coEvery {
searchResultGenerator.generateSearchResults(searchTerm, zimFileReader) searchResultGenerator.generateSearchResults(searchTerm, zimFileReader)
} returns search } returns suggestionSearch
viewModel.actions.trySend(Filter(searchTerm)).isSuccess viewModel.actions.trySend(Filter(searchTerm)).isSuccess
recentsFromDb.trySend(databaseResults).isSuccess recentsFromDb.trySend(databaseResults).isSuccess
viewModel.actions.trySend(ScreenWasStartedFrom(searchOrigin)).isSuccess viewModel.actions.trySend(ScreenWasStartedFrom(searchOrigin)).isSuccess

View File

@ -18,8 +18,8 @@
package org.kiwix.kiwixmobile.core.search.viewmodel package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.libzim.Entry import org.kiwix.libzim.SuggestionItem
internal class EntryWrapper : Entry() { class SuggestionItemWrapper : SuggestionItem() {
override fun getTitle(): String = super.getTitle() override fun getTitle(): String = super.getTitle()
} }

View File

@ -18,12 +18,11 @@
package org.kiwix.kiwixmobile.core.search.viewmodel package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.libzim.SearchIterator import org.kiwix.libzim.SuggestionIterator
// Create this as a helper class, as we can not directly use libkiwix/libzim functions in testing class SuggestionIteratorWrapper : SuggestionIterator() {
internal class SearchIteratorWrapper : SearchIterator() {
override fun remove() {} override fun remove() {}
override fun hasNext(): Boolean = super.hasNext() override fun hasNext(): Boolean = super.hasNext()
override fun next(): EntryWrapper = super.next() as EntryWrapper override fun next(): SuggestionItemWrapper = super.next() as SuggestionItemWrapper
} }

View File

@ -18,12 +18,11 @@
package org.kiwix.kiwixmobile.core.search.viewmodel package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.libzim.Search import org.kiwix.libzim.SuggestionSearch
// Create this as a helper class, as we can not directly use libkiwix/libzim functions in testing class SuggestionSearchWrapper : SuggestionSearch() {
internal class SearchWrapper : Search() {
override fun getEstimatedMatches(): Long = super.getEstimatedMatches() override fun getEstimatedMatches(): Long = super.getEstimatedMatches()
override fun getResults(start: Int, maxResults: Int): SearchIteratorWrapper = override fun getResults(start: Int, maxResults: Int): SuggestionIteratorWrapper =
super.getResults(start, maxResults) as SearchIteratorWrapper super.getResults(start, maxResults) as SuggestionIteratorWrapper
} }

View File

@ -44,11 +44,11 @@ internal class ZimSearchResultGeneratorTest {
@Test @Test
internal fun `suggestion results are distinct`() { internal fun `suggestion results are distinct`() {
val searchTerm = " " val searchTerm = " "
val searchWrapper: SearchWrapper = mockk() val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
every { zimFileReader.searchSuggestions(searchTerm) } returns searchWrapper every { zimFileReader.searchSuggestions(searchTerm) } returns suggestionSearchWrapper
runBlocking { runBlocking {
assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader)) assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader))
.isEqualTo(searchWrapper) .isEqualTo(suggestionSearchWrapper)
verify { verify {
zimFileReader.searchSuggestions(searchTerm) zimFileReader.searchSuggestions(searchTerm)
} }