Update the EH search engine to fix issues with the current search features
This commit is contained in:
parent
fb19f6b860
commit
4f803494ff
@ -58,11 +58,11 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
|
||||
*
|
||||
* @param list the list to set.
|
||||
*/
|
||||
suspend fun setItems(cScope: CoroutineScope, list: List<LibraryItem>) {
|
||||
suspend fun setItems(scope: CoroutineScope, list: List<LibraryItem>) {
|
||||
// A copy of manga always unfiltered.
|
||||
mangas = list.toList()
|
||||
|
||||
performFilter(cScope)
|
||||
performFilter(scope)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,12 +78,12 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
|
||||
// Note that we cannot use FlexibleAdapter's built in filtering system as we cannot cancel it
|
||||
// (well technically we can cancel it by invoking filterItems again but that doesn't work when
|
||||
// we want to perform a no-op filter)
|
||||
suspend fun performFilter(cScope: CoroutineScope) {
|
||||
suspend fun performFilter(scope: CoroutineScope) {
|
||||
lastFilterJob?.cancel()
|
||||
if (mangas.isNotEmpty() && searchText.isNotBlank()) {
|
||||
val savedSearchText = searchText
|
||||
|
||||
val job = cScope.launch(Dispatchers.IO) {
|
||||
val job = scope.launch(Dispatchers.IO) {
|
||||
val newManga = try {
|
||||
// Prepare filter object
|
||||
val parsedQuery = searchEngine.parseQuery(savedSearchText)
|
||||
@ -134,11 +134,11 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
|
||||
// Check if this manga even has metadata
|
||||
if (mangaWithMetaIds.binarySearch(mangaId) < 0) {
|
||||
// No meta? Filter using title
|
||||
item.filter(savedSearchText)
|
||||
} else false
|
||||
item.filter(savedSearchText to true)
|
||||
} else item.filter(savedSearchText to false)
|
||||
} else true
|
||||
} else {
|
||||
item.filter(savedSearchText)
|
||||
item.filter(savedSearchText to true)
|
||||
}
|
||||
}.toList()
|
||||
} catch (e: Exception) {
|
||||
|
@ -86,7 +86,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
|
||||
// EXH -->
|
||||
private var initialLoadHandle: LoadingHandle? = null
|
||||
lateinit var scope2: CoroutineScope
|
||||
private lateinit var supervisorScope: CoroutineScope
|
||||
|
||||
private fun newScope() = object : CoroutineScope {
|
||||
override val coroutineContext = SupervisorJob() + Dispatchers.Main
|
||||
@ -150,7 +150,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
// SY <--
|
||||
|
||||
// EXH -->
|
||||
scope2 = newScope()
|
||||
supervisorScope = newScope()
|
||||
initialLoadHandle = controller.loaderManager.openProgressBar()
|
||||
// EXH <--
|
||||
|
||||
@ -161,7 +161,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
// EXH -->
|
||||
scope2.launch {
|
||||
supervisorScope.launch {
|
||||
val handle = controller.loaderManager.openProgressBar()
|
||||
try {
|
||||
// EXH <--
|
||||
@ -177,7 +177,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
subscriptions += controller.libraryMangaRelay
|
||||
.subscribe {
|
||||
// EXH -->
|
||||
scope2.launch {
|
||||
supervisorScope.launch {
|
||||
try {
|
||||
// EXH <--
|
||||
onNextLibraryManga(this, it)
|
||||
@ -249,7 +249,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
fun unsubscribe() {
|
||||
subscriptions.clear()
|
||||
// EXH -->
|
||||
scope2.cancel()
|
||||
supervisorScope.cancel()
|
||||
controller.loaderManager.closeProgressBar(initialLoadHandle)
|
||||
// EXH <--
|
||||
}
|
||||
|
@ -19,18 +19,26 @@ import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
import exh.isNamespaceSource
|
||||
import exh.metadata.metadata.base.RaisedTag
|
||||
import exh.util.SourceTagsUtil.Companion.TAG_TYPE_EXCLUDE
|
||||
import exh.util.SourceTagsUtil.Companion.getRaisedTags
|
||||
import exh.util.SourceTagsUtil.Companion.parseTag
|
||||
import kotlinx.android.synthetic.main.source_compact_grid_item.view.card
|
||||
import kotlinx.android.synthetic.main.source_compact_grid_item.view.gradient
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Preference<DisplayMode>) :
|
||||
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
|
||||
AbstractFlexibleItem<LibraryHolder>(), IFilterable<Pair<String, Boolean>> {
|
||||
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
// SY -->
|
||||
private val trackManager: TrackManager = Injekt.get()
|
||||
private val db: DatabaseHelper = Injekt.get()
|
||||
private val source by lazy {
|
||||
sourceManager.get(manga.source)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
var downloadCount = -1
|
||||
@ -96,38 +104,13 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
|
||||
* @param constraint the query to apply.
|
||||
* @return true if the manga should be included, false otherwise.
|
||||
*/
|
||||
override fun filter(constraint: String): Boolean {
|
||||
return manga.title.contains(constraint, true) ||
|
||||
(manga.author?.contains(constraint, true) ?: false) ||
|
||||
(manga.artist?.contains(constraint, true) ?: false) ||
|
||||
sourceManager.getOrStub(manga.source).name.contains(constraint, true) ||
|
||||
(Injekt.get<TrackManager>().hasLoggedServices() && filterTracks(constraint, db.getTracks(manga).executeAsBlocking())) ||
|
||||
if (constraint.contains(" ") || constraint.contains("\"")) {
|
||||
val genres = manga.genre?.split(", ")?.map {
|
||||
it.drop(it.indexOfFirst { it == ':' } + 1).toLowerCase().trim() // tachiEH tag namespaces
|
||||
}
|
||||
var clean_constraint = ""
|
||||
var ignorespace = false
|
||||
for (i in constraint.trim().toLowerCase()) {
|
||||
if (i == ' ') {
|
||||
if (!ignorespace) {
|
||||
clean_constraint = clean_constraint + ","
|
||||
} else {
|
||||
clean_constraint = clean_constraint + " "
|
||||
}
|
||||
} else if (i == '"') {
|
||||
ignorespace = !ignorespace
|
||||
} else {
|
||||
clean_constraint = clean_constraint + Character.toString(i)
|
||||
}
|
||||
}
|
||||
clean_constraint.split(",").all { containsGenre(it.trim(), genres) }
|
||||
} else containsGenre(
|
||||
constraint,
|
||||
manga.genre?.split(", ")?.map {
|
||||
it.drop(it.indexOfFirst { it == ':' } + 1).toLowerCase().trim() // tachiEH tag namespaces
|
||||
}
|
||||
)
|
||||
override fun filter(constraint: Pair<String, Boolean>): Boolean {
|
||||
return manga.title.contains(constraint.first, true) ||
|
||||
(manga.author?.contains(constraint.first, true) ?: false) ||
|
||||
(manga.artist?.contains(constraint.first, true) ?: false) ||
|
||||
(source?.name?.contains(constraint.first, true) ?: false) ||
|
||||
(Injekt.get<TrackManager>().hasLoggedServices() && filterTracks(constraint.first, db.getTracks(manga).executeAsBlocking())) ||
|
||||
constraint.second && ehContainsGenre(constraint.first)
|
||||
}
|
||||
|
||||
private fun filterTracks(constraint: String, tracks: List<Track>): Boolean {
|
||||
@ -141,6 +124,54 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
|
||||
return@any false
|
||||
}
|
||||
}
|
||||
|
||||
private fun ehContainsGenre(constraint: String): Boolean {
|
||||
val genres = manga.getGenres()
|
||||
val raisedTags = if (source?.isNamespaceSource() == true) {
|
||||
manga.getRaisedTags(genres)
|
||||
} else null
|
||||
return if (constraint.contains(" ") || constraint.contains("\"")) {
|
||||
var cleanConstraint = ""
|
||||
var ignoreSpace = false
|
||||
for (i in constraint.trim().toLowerCase()) {
|
||||
when (i) {
|
||||
' ' -> {
|
||||
cleanConstraint = if (!ignoreSpace) {
|
||||
"$cleanConstraint,"
|
||||
} else {
|
||||
"$cleanConstraint "
|
||||
}
|
||||
}
|
||||
'"' -> {
|
||||
ignoreSpace = !ignoreSpace
|
||||
}
|
||||
else -> {
|
||||
cleanConstraint += i.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanConstraint.split(",").all {
|
||||
if (raisedTags == null) containsGenre(it.trim(), genres) else containsRaisedGenre(
|
||||
parseTag(it.trim()), raisedTags
|
||||
)
|
||||
}
|
||||
} else if (raisedTags == null) {
|
||||
containsGenre(constraint, genres)
|
||||
} else {
|
||||
containsRaisedGenre(parseTag(constraint), raisedTags)
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsRaisedGenre(tag: RaisedTag, genres: List<RaisedTag>): Boolean {
|
||||
val genre = genres.find {
|
||||
(it.namespace?.toLowerCase() == tag.namespace?.toLowerCase() && it.name.toLowerCase() == tag.name.toLowerCase())
|
||||
}
|
||||
return if (tag.type == TAG_TYPE_EXCLUDE) {
|
||||
genre == null
|
||||
} else {
|
||||
genre != null
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
private fun containsGenre(tag: String, genres: List<String>?): Boolean {
|
||||
|
@ -7,7 +7,7 @@ import exh.metadata.sql.tables.SearchTitleTable
|
||||
class SearchEngine {
|
||||
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
|
||||
|
||||
fun textToSubQueries(
|
||||
private fun textToSubQueries(
|
||||
namespace: String?,
|
||||
component: Text?
|
||||
): Pair<String, List<String>>? {
|
||||
@ -20,42 +20,46 @@ class SearchEngine {
|
||||
}
|
||||
val componentTagQuery = maybeLenientComponent?.let {
|
||||
val params = mutableListOf<String>()
|
||||
it.map { q ->
|
||||
it.joinToString(separator = " OR ", prefix = "(", postfix = ")") { q ->
|
||||
params += q
|
||||
"${SearchTagTable.TABLE}.${SearchTagTable.COL_NAME} LIKE ?"
|
||||
}.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params
|
||||
} to params
|
||||
}
|
||||
return if (namespace != null) {
|
||||
var query =
|
||||
"""
|
||||
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
||||
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
||||
""".trimIndent()
|
||||
val params = mutableListOf(escapeLike(namespace))
|
||||
if (componentTagQuery != null) {
|
||||
query += "\n AND ${componentTagQuery.first}"
|
||||
params += componentTagQuery.second
|
||||
return when {
|
||||
namespace != null -> {
|
||||
var query =
|
||||
"""
|
||||
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
||||
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
||||
""".trimIndent()
|
||||
val params = mutableListOf(escapeLike(namespace))
|
||||
if (componentTagQuery != null) {
|
||||
query += "\n AND ${componentTagQuery.first}"
|
||||
params += componentTagQuery.second
|
||||
}
|
||||
|
||||
"$query)" to params
|
||||
}
|
||||
component != null -> {
|
||||
// Match title + tags
|
||||
val tagQuery =
|
||||
"""
|
||||
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||
WHERE ${componentTagQuery!!.first}
|
||||
""".trimIndent() to componentTagQuery.second
|
||||
|
||||
"$query)" to params
|
||||
} else if (component != null) {
|
||||
// Match title + tags
|
||||
val tagQuery =
|
||||
"""
|
||||
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||
WHERE ${componentTagQuery!!.first}
|
||||
""".trimIndent() to componentTagQuery.second
|
||||
val titleQuery =
|
||||
"""
|
||||
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
||||
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
||||
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
||||
|
||||
val titleQuery =
|
||||
"""
|
||||
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
||||
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
||||
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
||||
|
||||
"(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to
|
||||
(tagQuery.second + titleQuery.second)
|
||||
} else null
|
||||
"(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to
|
||||
(tagQuery.second + titleQuery.second)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun queryToSql(q: List<QueryComponent>): Pair<String, List<String>> {
|
||||
@ -158,7 +162,7 @@ class SearchEngine {
|
||||
}
|
||||
}
|
||||
|
||||
for (char in query.toLowerCase()) {
|
||||
query.toLowerCase().forEach { char ->
|
||||
if (char == '"') {
|
||||
inQuotes = !inQuotes
|
||||
} else if (enableWildcard && (char == '?' || char == '_')) {
|
||||
@ -167,7 +171,7 @@ class SearchEngine {
|
||||
} else if (enableWildcard && (char == '*' || char == '%')) {
|
||||
flushText()
|
||||
queuedText.add(MultiWildcard(char.toString()))
|
||||
} else if (char == '-') {
|
||||
} else if (char == '-' && !inQuotes && (queuedRawText.isBlank() || queuedRawText.last() == ' ')) {
|
||||
nextIsExcluded = true
|
||||
} else if (char == '$') {
|
||||
nextIsExact = true
|
||||
|
@ -48,9 +48,21 @@ class SourceTagsUtil {
|
||||
"$namespace:$tag"
|
||||
}
|
||||
companion object {
|
||||
fun Manga.getRaisedTags(): List<RaisedTag>? = this.getGenres()?.map { parseTag(it) }
|
||||
fun Manga.getRaisedTags(genres: List<String>? = null): List<RaisedTag>? = (genres ?: this.getGenres())?.map { parseTag(it) }
|
||||
|
||||
fun parseTag(tag: String) = RaisedTag(tag.substringBefore(':').trimOrNull(), (tag.substringAfter(':').trimOrNull() ?: tag), TAG_TYPE_DEFAULT)
|
||||
fun parseTag(tag: String) = RaisedTag(
|
||||
(
|
||||
if (tag.startsWith("-")) {
|
||||
tag.substringAfter("-")
|
||||
} else {
|
||||
tag
|
||||
}
|
||||
).substringBefore(':', missingDelimiterValue = "").trimOrNull(),
|
||||
tag.substringAfter(':', missingDelimiterValue = tag).trim(),
|
||||
if (tag.startsWith("-")) TAG_TYPE_EXCLUDE else TAG_TYPE_DEFAULT
|
||||
)
|
||||
|
||||
const val TAG_TYPE_EXCLUDE = 69 // why not
|
||||
|
||||
const val DOUJINSHI_COLOR = "#f44336"
|
||||
const val MANGA_COLOR = "#ff9800"
|
||||
|
Loading…
x
Reference in New Issue
Block a user