Clean up SoneTemplatePage’s constructors
[Sone.git] / src / main / kotlin / net / pterodactylus / sone / web / pages / SearchPage.kt
index 5f40238..7cddc35 100644 (file)
@@ -3,11 +3,13 @@ package net.pterodactylus.sone.web.pages
 import com.google.common.base.Ticker
 import com.google.common.cache.Cache
 import com.google.common.cache.CacheBuilder
+import freenet.support.Logger
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.Pagination
 import net.pterodactylus.sone.utils.emptyToNull
+import net.pterodactylus.sone.utils.memoize
 import net.pterodactylus.sone.utils.paginate
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -26,11 +28,12 @@ import java.util.concurrent.TimeUnit.MINUTES
  * words.
  */
 class SearchPage @JvmOverloads constructor(template: Template, webInterface: WebInterface, ticker: Ticker = Ticker.systemTicker()):
-               SoneTemplatePage("search.html", template, "Page.Search.Title", webInterface, false) {
+               SoneTemplatePage("search.html", webInterface, template, "Page.Search.Title") {
 
        private val cache: Cache<Iterable<Phrase>, Pagination<Post>> = CacheBuilder.newBuilder().ticker(ticker).expireAfterAccess(5, MINUTES).build()
 
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+               val startTime = System.currentTimeMillis()
                val phrases = try {
                        freenetRequest.parameters["query"].emptyToNull?.parse()
                } catch (te: TextException) {
@@ -43,24 +46,26 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                        1 -> phrases.first().phrase.also { word ->
                                when {
                                        word.removePrefix("sone://").let(webInterface.core::getSone) != null -> redirect("viewSone.html?sone=${word.removePrefix("sone://")}")
-                                       word.removePrefix("post://").let(webInterface.core::getPost).isPresent -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
-                                       word.removePrefix("reply://").let(webInterface.core::getPostReply).isPresent -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(webInterface.core::getPostReply).get().postId}")
+                                       word.removePrefix("post://").let(webInterface.core::getPost) != null -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
+                                       word.removePrefix("reply://").let(webInterface.core::getPostReply) != null -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(webInterface.core::getPostReply)?.postId}")
                                        word.removePrefix("album://").let(webInterface.core::getAlbum) != null -> redirect("imageBrowser.html?album=${word.removePrefix("album://")}")
                                        word.removePrefix("image://").let { webInterface.core.getImage(it, false) } != null -> redirect("imageBrowser.html?image=${word.removePrefix("image://")}")
                                }
                        }
                }
 
+               val soneNameCache = { sone: Sone -> sone.names() }.memoize()
                val sonePagination = webInterface.core.sones
-                               .scoreAndPaginate(phrases) { it.allText() }
+                               .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
                                .apply { page = freenetRequest.parameters["sonePage"].emptyToNull?.toIntOrNull() ?: 0 }
                val postPagination = cache.get(phrases) {
                        webInterface.core.sones
                                        .flatMap(Sone::getPosts)
                                        .filter { Post.FUTURE_POSTS_FILTER.apply(it) }
-                                       .scoreAndPaginate(phrases) { it.allText() }
+                                       .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
                }.apply { page = freenetRequest.parameters["postPage"].emptyToNull?.toIntOrNull() ?: 0 }
 
+               Logger.normal(SearchPage::class.java, "Finished search for “${freenetRequest.parameters["query"]}” in ${System.currentTimeMillis() - startTime}ms.")
                templateContext["sonePagination"] = sonePagination
                templateContext["soneHits"] = sonePagination.items
                templateContext["postPagination"] = postPagination
@@ -75,17 +80,19 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                                        .paginate(webInterface.core.preferences.postsPerPage)
 
        private fun Sone.names() =
-                       listOf(name, profile.firstName, profile.middleName, profile.lastName)
-                                       .filterNotNull()
-                                       .joinToString("")
+                       with(profile) {
+                               listOf(name, firstName, middleName, lastName)
+                                               .filterNotNull()
+                                               .joinToString("")
+                       }
 
-       private fun Sone.allText() =
-                       (names() + profile.fields.map { "${it.name} ${it.value}" }.joinToString(" ", " ")).toLowerCase()
+       private fun Sone.allText(soneNameCache: (Sone) -> String) =
+                       (soneNameCache(this) + profile.fields.map { "${it.name} ${it.value}" }.joinToString(" ", " ")).toLowerCase()
 
-       private fun Post.allText() =
-                       (text + recipient.orNull()?.let { " ${it.names()}" } + webInterface.core.getReplies(id)
+       private fun Post.allText(soneNameCache: (Sone) -> String) =
+                       (text + recipient.orNull()?.let { " ${soneNameCache(it)}" } + webInterface.core.getReplies(id)
                                        .filter { PostReply.FUTURE_REPLY_FILTER.apply(it) }
-                                       .map { "${it.sone.names()} ${it.text}" }.joinToString(" ", " ")).toLowerCase()
+                                       .map { "${soneNameCache(it.sone)} ${it.text}" }.joinToString(" ", " ")).toLowerCase()
 
        private fun score(text: String, phrases: Iterable<Phrase>): Double {
                val requiredPhrases = phrases.count { it.required }
@@ -106,15 +113,12 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                return requiredHits * 3 + optionalHits + (requiredHits - requiredPhrases) * 5 - (forbiddenHits * 2)
        }
 
-       private fun String.findAll(needle: String): List<Int> {
-               var nextIndex = indexOf(needle)
-               val positions = mutableListOf<Int>()
-               while (nextIndex != -1) {
-                       positions += nextIndex
-                       nextIndex = indexOf(needle, nextIndex + 1)
-               }
-               return positions
-       }
+       private fun String.findAll(needle: String) =
+                       generateSequence(indexOf(needle).takeIf { it > -1 }) { lastPosition ->
+                               lastPosition
+                                               .let { indexOf(needle, it + 1) }
+                                               .takeIf { it > -1 }
+                       }.toList()
 
        private fun String.parse() =
                        StringEscaper.parseLine(this)