mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Implement search engine.
This commit is contained in:
		| @@ -200,6 +200,9 @@ dependencies { | ||||
|     //SnappyDB (EH) | ||||
|     compile 'io.paperdb:paperdb:2.0' | ||||
|  | ||||
|     //JVE (Regex) (EH) | ||||
|     compile 'ru.lanwen.verbalregex:java-verbal-expressions:1.4' | ||||
|  | ||||
|     // Tests | ||||
|     //Paper DB screws up tests | ||||
|     /*testCompile 'junit:junit:4.12' | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource | ||||
| import exh.metadata.MetadataHelper | ||||
| import exh.metadata.copyTo | ||||
| import exh.metadata.models.ExGalleryMetadata | ||||
| import exh.search.SearchEngine | ||||
| import okhttp3.Response | ||||
| import rx.Observable | ||||
|  | ||||
| @@ -24,6 +25,8 @@ class EHentaiMetadata(override val id: Int, | ||||
|  | ||||
|     val internalEx = EHentai(id - 2, exh, context) | ||||
|  | ||||
|     val searchEngine = SearchEngine() | ||||
|  | ||||
|     override val baseUrl: String | ||||
|         get() = throw UnsupportedOperationException() | ||||
|     override val lang: String | ||||
| @@ -105,8 +108,11 @@ class EHentaiMetadata(override val id: Int, | ||||
|  | ||||
|     override fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>) | ||||
|     = Observable.fromCallable { | ||||
|         val parsed = searchEngine.parseQuery(query) | ||||
|         page.mangas.addAll(sortedByTimeGalleries().filter { manga -> | ||||
|             filters.isEmpty() || filters.filter { it.id == manga.genre }.isNotEmpty() | ||||
|         }.filter { | ||||
|             searchEngine.matches(it, parsed) | ||||
|         }.mapToManga()) | ||||
|         page | ||||
|     }!! | ||||
|   | ||||
							
								
								
									
										5
									
								
								app/src/main/java/exh/VerbelExpressionExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/java/exh/VerbelExpressionExtensions.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| package exh | ||||
|  | ||||
| import ru.lanwen.verbalregex.VerbalExpression | ||||
|  | ||||
| fun VerbalExpression.Builder.anyChar() = add(".")!! | ||||
							
								
								
									
										3
									
								
								app/src/main/java/exh/search/MultiWildcard.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/src/main/java/exh/search/MultiWildcard.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| package exh.search | ||||
|  | ||||
| class MultiWildcard : TextComponent() | ||||
							
								
								
									
										4
									
								
								app/src/main/java/exh/search/Namespace.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/src/main/java/exh/search/Namespace.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| package exh.search | ||||
|  | ||||
| class Namespace(var namespace: Text, | ||||
|                 var tag: Text? = null) : QueryComponent() | ||||
							
								
								
									
										6
									
								
								app/src/main/java/exh/search/QueryComponent.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/src/main/java/exh/search/QueryComponent.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package exh.search | ||||
|  | ||||
| open class QueryComponent { | ||||
|     var excluded = false | ||||
|     var exact = false | ||||
| } | ||||
							
								
								
									
										124
									
								
								app/src/main/java/exh/search/SearchEngine.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								app/src/main/java/exh/search/SearchEngine.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| package exh.search | ||||
|  | ||||
| import exh.metadata.models.ExGalleryMetadata | ||||
| import exh.metadata.models.Tag | ||||
| import ru.lanwen.verbalregex.VerbalExpression | ||||
| import java.util.* | ||||
|  | ||||
| class SearchEngine { | ||||
|     //TODO Namespace alias | ||||
|     fun matches(metadata: ExGalleryMetadata, query: List<QueryComponent>): Boolean { | ||||
|  | ||||
|         fun matchTagList(tags: List<Tag>, | ||||
|                          component: Text, | ||||
|                          builder: VerbalExpression.Builder, | ||||
|                          built: VerbalExpression): Boolean { | ||||
|             //Match tags | ||||
|             val tagMatcher = if(!component.exact) | ||||
|                 builder.anything().build() | ||||
|             else | ||||
|                 built | ||||
|             //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 | ||||
|         } | ||||
|  | ||||
|         for(component in query) { | ||||
|             if(component is Text) { | ||||
|                 val builder = component.asRegex() | ||||
|                 val built = builder.build() | ||||
|                 //Match title | ||||
|                 if (built.test(metadata.title?.toLowerCase()) | ||||
|                         || built.test(metadata.altTitle?.toLowerCase())) { | ||||
|                     continue | ||||
|                 } | ||||
|                 //Match tags | ||||
|                 if(!matchTagList(metadata.tags.entries.flatMap(MutableMap.MutableEntry<String, ArrayList<Tag>>::value), | ||||
|                         component, | ||||
|                         builder, | ||||
|                         built)) | ||||
|                     return false | ||||
|             } else if(component is Namespace) { | ||||
|                 //Match namespace | ||||
|                 val ns = metadata.tags.entries.filter { | ||||
|                     it.key == component.namespace.rawTextOnly() | ||||
|                 }.flatMap { it.value } | ||||
|                 //Match tags | ||||
|                 val builder = component.tag!!.asRegex() | ||||
|                 val built = builder.build() | ||||
|                 if(!matchTagList(ns, component.tag!!, builder, built)) | ||||
|                     return false | ||||
|             } | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     fun parseQuery(query: String): List<QueryComponent> { | ||||
|         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() | ||||
|                 namespace = Namespace(flushToText(), null) | ||||
|             } else if(char == ' ' && !inQuotes) { | ||||
|                 flushAll() | ||||
|             } else { | ||||
|                 queuedRawText.append(char) | ||||
|             } | ||||
|         } | ||||
|         flushAll() | ||||
|  | ||||
|         return res | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3
									
								
								app/src/main/java/exh/search/SingleWildcard.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/src/main/java/exh/search/SingleWildcard.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| package exh.search | ||||
|  | ||||
| class SingleWildcard : TextComponent() | ||||
							
								
								
									
										3
									
								
								app/src/main/java/exh/search/StringTextComponent.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/src/main/java/exh/search/StringTextComponent.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| package exh.search | ||||
|  | ||||
| class StringTextComponent(val value: String) : TextComponent() | ||||
							
								
								
									
										24
									
								
								app/src/main/java/exh/search/Text.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/src/main/java/exh/search/Text.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package exh.search | ||||
|  | ||||
| import exh.anyChar | ||||
| import ru.lanwen.verbalregex.VerbalExpression | ||||
|  | ||||
| class Text: QueryComponent() { | ||||
|     val components = mutableListOf<TextComponent>() | ||||
|  | ||||
|     fun asRegex(): VerbalExpression.Builder { | ||||
|         val builder = VerbalExpression.regex() | ||||
|         for(component in components) { | ||||
|             when(component) { | ||||
|                 is StringTextComponent -> builder.then(component.value) | ||||
|                 is SingleWildcard -> builder.anyChar() | ||||
|                 is MultiWildcard -> builder.zeroOrMore() | ||||
|             } | ||||
|         } | ||||
|         return builder | ||||
|     } | ||||
|  | ||||
|     fun rawTextOnly() = components | ||||
|             .filter { it is StringTextComponent } | ||||
|             .joinToString(separator = "") | ||||
| } | ||||
							
								
								
									
										3
									
								
								app/src/main/java/exh/search/TextComponent.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/src/main/java/exh/search/TextComponent.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| package exh.search | ||||
|  | ||||
| open class TextComponent | ||||
		Reference in New Issue
	
	Block a user