NerdNumber9 f5c4535cb0 Add migration ability.
Various bug fixes and code cleanup.
2017-01-04 22:56:24 -05:00

140 lines
4.6 KiB
Kotlin

package exh.search
import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.Tag
class SearchEngine {
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
fun matches(metadata: ExGalleryMetadata, query: List<QueryComponent>): Boolean {
fun matchTagList(tags: Sequence<Tag>,
component: Text): Boolean {
//Match tags
val tagMatcher = if(!component.exact)
component.asLenientRegex()
else
component.asRegex()
//Match beginning of tag
if (tags.find {
tagMatcher.testExact(it.name)
} != null) {
if(component.excluded) return false
} else {
//No tag matched for this component
return false
}
return true
}
val cachedLowercaseTitle = metadata.title?.toLowerCase()
val cachedLowercaseAltTitle = metadata.altTitle?.toLowerCase()
for(component in query) {
if(component is Text) {
//Match title
if (component.asRegex().test(cachedLowercaseTitle)
|| component.asRegex().test(cachedLowercaseAltTitle)) {
continue
}
//Match tags
if(!matchTagList(metadata.tags.entries.asSequence().flatMap { it.value.asSequence() },
component)) return false
} else if(component is Namespace) {
if(component.namespace == "uploader") {
//Match uploader
if(!component.tag?.rawTextOnly().equals(metadata.uploader,
ignoreCase = true)) {
return false
}
} else {
//Match namespace
val ns = metadata.tags.entries.asSequence().filter {
it.key == component.namespace
}.flatMap { it.value.asSequence() }
//Match tags
if (!matchTagList(ns, component.tag!!))
return false
}
}
}
return true
}
fun parseQuery(query: String) = queryCache.getOrPut(query, {
val res = mutableListOf<QueryComponent>()
var inQuotes = false
val queuedRawText = StringBuilder()
val queuedText = mutableListOf<TextComponent>()
var namespace: Namespace? = null
var nextIsExcluded = false
var nextIsExact = false
fun flushText() {
if(queuedRawText.isNotEmpty()) {
queuedText += StringTextComponent(queuedRawText.toString())
queuedRawText.setLength(0)
}
}
fun flushToText() = Text().apply {
components += queuedText
queuedText.clear()
}
fun flushAll() {
flushText()
if (queuedText.isNotEmpty()) {
val component = namespace?.apply {
tag = flushToText()
} ?: flushToText()
component.excluded = nextIsExcluded
component.exact = nextIsExact
res += component
}
}
for(char in query.toLowerCase()) {
if(char == '"') {
inQuotes = !inQuotes
} else if(char == '?' || char == '_') {
flushText()
queuedText.add(SingleWildcard())
} else if(char == '*' || char == '%') {
flushText()
queuedText.add(MultiWildcard())
} else if(char == '-') {
nextIsExcluded = true
} else if(char == '$') {
nextIsExact = true
} else if(char == ':') {
flushText()
var flushed = flushToText().rawTextOnly()
//Map tag aliases
flushed = when(flushed) {
"a" -> "artist"
"c", "char" -> "character"
"f" -> "female"
"g", "creator", "circle" -> "group"
"l", "lang" -> "language"
"m" -> "male"
"p", "series" -> "parody"
"r" -> "reclass"
else -> flushed
}
namespace = Namespace(flushed, null)
} else if(char == ' ' && !inQuotes) {
flushAll()
} else {
queuedRawText.append(char)
}
}
flushAll()
res
})
}