Redo the EH library search engine, make it work for every manga, meaning exclusion, partial matching, and a bunch of other things now working the library search

This commit is contained in:
Jobobby04 2020-08-04 23:34:26 -04:00
parent bb87392eef
commit 64c5b70c78

View File

@ -1,14 +1,21 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.category.CategoryAdapter import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import exh.isLewdSource import exh.isLewdSource
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle
import exh.search.Namespace
import exh.search.QueryComponent
import exh.search.SearchEngine import exh.search.SearchEngine
import exh.search.Text
import exh.util.await import exh.util.await
import exh.util.cancellable import exh.util.cancellable
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -35,6 +42,11 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
private val searchEngine = SearchEngine() private val searchEngine = SearchEngine()
private var lastFilterJob: Job? = null private var lastFilterJob: Job? = null
private val sourceManager: SourceManager by injectLazy()
private val trackManager: TrackManager by injectLazy()
private val hasLoggedServices by lazy {
trackManager.hasLoggedServices()
}
// Keep compatibility as searchText field was replaced when we upgraded FlexibleAdapter // Keep compatibility as searchText field was replaced when we upgraded FlexibleAdapter
var searchText var searchText
@ -87,15 +99,6 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
val newManga = try { val newManga = try {
// Prepare filter object // Prepare filter object
val parsedQuery = searchEngine.parseQuery(savedSearchText) val parsedQuery = searchEngine.parseQuery(savedSearchText)
val sqlQuery = searchEngine.queryToSql(parsedQuery)
val queryResult = db.lowLevel().rawQuery(
RawQuery.builder()
.query(sqlQuery.first)
.args(*sqlQuery.second.toTypedArray())
.build()
)
ensureActive() // Fail early when cancelled
val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await() val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await()
val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count) val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count)
@ -112,33 +115,20 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
ensureActive() // Fail early when cancelled ensureActive() // Fail early when cancelled
val convertedResult = LongArray(queryResult.count)
if (convertedResult.isNotEmpty()) {
val mangaIdCol = queryResult.getColumnIndex(SearchMetadataTable.COL_MANGA_ID)
queryResult.moveToFirst()
while (!queryResult.isAfterLast) {
ensureActive() // Fail early when cancelled
convertedResult[queryResult.position] = queryResult.getLong(mangaIdCol)
queryResult.moveToNext()
}
}
ensureActive() // Fail early when cancelled
// Flow the mangas to allow cancellation of this filter operation // Flow the mangas to allow cancellation of this filter operation
mangas.asFlow().cancellable().filter { item -> mangas.asFlow().cancellable().filter { item ->
if (isLewdSource(item.manga.source)) { if (isLewdSource(item.manga.source)) {
val mangaId = item.manga.id ?: -1 val mangaId = item.manga.id ?: -1
if (convertedResult.binarySearch(mangaId) < 0) {
// Check if this manga even has metadata
if (mangaWithMetaIds.binarySearch(mangaId) < 0) { if (mangaWithMetaIds.binarySearch(mangaId) < 0) {
// No meta? Filter using title // No meta? Filter using title
item.filter(savedSearchText to true) filterManga(parsedQuery, item.manga)
} else item.filter(savedSearchText to false)
} else true
} else { } else {
item.filter(savedSearchText to true) val tags = db.getSearchTagsForManga(mangaId).await()
val titles = db.getSearchTitlesForManga(mangaId).await()
filterManga(parsedQuery, item.manga, false, tags, titles)
}
} else {
filterManga(parsedQuery, item.manga)
} }
}.toList() }.toList()
} catch (e: Exception) { } catch (e: Exception) {
@ -159,5 +149,82 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
updateDataSet(mangas) updateDataSet(mangas)
} }
} }
private suspend fun filterManga(queries: List<QueryComponent>, manga: LibraryManga, checkGenre: Boolean = true, searchTags: List<SearchTag>? = null, searchTitles: List<SearchTitle>? = null): Boolean {
val mappedQueries = queries.groupBy { it.excluded }
val tracks = if (hasLoggedServices) db.getTracks(manga).await().toList() else null
val source = sourceManager.get(manga.source)
val genre = if (checkGenre) manga.getGenres() else null
val hasNormalQuery = mappedQueries[false]?.all { queryComponent ->
when (queryComponent) {
is Text -> {
val query = queryComponent.asQuery()
manga.title.contains(query, true) ||
(manga.author?.contains(query, true) == true) ||
(manga.artist?.contains(query, true) == true) ||
(source?.name?.contains(query, true) == true) ||
(hasLoggedServices && tracks != null && filterTracks(query, tracks)) ||
(genre != null && genre.any { it.contains(query, true) }) ||
(searchTags != null && searchTags.any { it.name.contains(query, true) }) ||
(searchTitles != null && searchTitles.any { it.title.contains(query, true) })
}
is Namespace -> {
searchTags != null && searchTags.any {
val tag = queryComponent.tag
(it.namespace != null && it.namespace.contains(queryComponent.namespace, true) && tag != null && it.name.contains(tag.asQuery(), true)) ||
(tag == null && it.namespace != null && it.namespace.contains(queryComponent.namespace, true))
}
}
else -> true
}
}
val doesNotHaveExcludedQuery = mappedQueries[true]?.all { queryComponent ->
when (queryComponent) {
is Text -> {
val query = queryComponent.asQuery()
query.isBlank() || (
(!manga.title.contains(query, true)) &&
(manga.author == null || (manga.author?.contains(query, true) == false)) &&
(manga.artist == null || (manga.artist?.contains(query, true) == false)) &&
(source == null || !source.name.contains(query, true)) &&
(hasLoggedServices && tracks != null && !filterTracks(query, tracks)) &&
(genre == null || genre.all { !it.contains(query, true) }) &&
(searchTags == null || searchTags.all { !it.name.contains(query, true) }) ||
(searchTitles == null || searchTitles.all { !it.title.contains(query, true) })
)
}
is Namespace -> {
Timber.d(manga.title)
val tag = queryComponent.tag?.asQuery()
searchTags == null || searchTags.all {
if (tag == null || tag.isBlank()) {
it.namespace == null || !it.namespace.contains(queryComponent.namespace, true)
} else if (it.namespace == null) {
true
} else {
!(it.name.contains(tag, true) && it.namespace.contains(queryComponent.namespace, true))
}
}
}
else -> true
}
}
return (hasNormalQuery != null && doesNotHaveExcludedQuery != null && hasNormalQuery && doesNotHaveExcludedQuery) ||
(hasNormalQuery != null && doesNotHaveExcludedQuery == null && hasNormalQuery) ||
(hasNormalQuery == null && doesNotHaveExcludedQuery != null && doesNotHaveExcludedQuery)
}
private fun filterTracks(constraint: String, tracks: List<Track>): Boolean {
return tracks.any {
val trackService = trackManager.getService(it.sync_id)
if (trackService != null) {
val status = trackService.getStatus(it.status)
val name = trackService.name
return@any status.contains(constraint, true) || name.contains(constraint, true)
}
return@any false
}
}
// EXH <-- // EXH <--
} }