🔀 Merge next add-audio-player
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 21 Nov 2019 06:20:05 +0000 (07:20 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 21 Nov 2019 06:22:57 +0000 (07:22 +0100)
src/main/kotlin/net/pterodactylus/sone/core/DefaultElementLoader.kt
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt
src/main/resources/templates/linked/audio.html [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt

index 409d18c..eb90b34 100644 (file)
@@ -28,6 +28,13 @@ class DefaultElementLoader(private val freenetInterface: FreenetInterface, ticke
        private val elementCache: Cache<String, LinkedElement> = CacheBuilder.newBuilder().build()
        private val callback = object: FreenetInterface.BackgroundFetchCallback {
                override fun shouldCancel(uri: FreenetURI, mimeType: String, size: Long): Boolean {
+                       if (mimeType.startsWith("audio/")) {
+                               elementCache.get(uri.toString().decode().normalize()) {
+                                       LinkedElement(uri.toString(), properties = mapOf(
+                                                       "type" to "audio", "size" to size, "sizeHuman" to size.human
+                                       ))
+                               }
+                       }
                        return (size > 2097152) || (!mimeType.startsWith("image/") && !mimeType.startsWith("text/html"))
                }
 
@@ -74,13 +81,15 @@ class DefaultElementLoader(private val freenetInterface: FreenetInterface, ticke
                                        .firstOrNull { !it.first.tagName().startsWith("h", ignoreCase = true) }
                                        ?.second
 
-               private val Int.human get() = when (this) {
+               private val Long.human get() = when (this) {
                        in 0..1023 -> "$this B"
                        in 1024..1048575 -> "${this / 1024} KiB"
                        in 1048576..1073741823 -> "${this / 1048576} MiB"
                        else -> "${this / 1073741824} GiB"
                }
 
+               private val Int.human get() = toLong().human
+
                override fun failed(uri: FreenetURI) {
                        failureCache.put(uri.toString().decode().normalize(), true)
                        removeLoadingLink(uri)
index 200bd83..5cc602c 100644 (file)
@@ -20,34 +20,45 @@ class LinkedElementRenderFilter : Filter {
                                data is LinkedElement && data.loading -> renderNotLoadedLinkedElement(data)
                                data is LinkedElement && data.properties["type"] == "image" -> renderLinkedImage(data)
                                data is LinkedElement && data.properties["type"] == "html" -> renderHtmlPage(data)
+                               data is LinkedElement && data.properties["type"] == "audio" -> renderAudioPlayer(data)
                                else -> null
                        }
 
        private fun renderLinkedImage(linkedElement: LinkedElement) =
-                       StringWriter().use {
-                               val templateContext = templateContextFactory.createTemplateContext()
+                       renderTemplate(loadedImageTemplate) { templateContext ->
                                templateContext["link"] = linkedElement.link
-                               it.also { loadedImageTemplate.render(templateContext, it) }
-                       }.toString()
+                       }
 
        private fun renderHtmlPage(linkedElement: LinkedElement) =
-                       StringWriter().use {
-                               val templateContext = templateContextFactory.createTemplateContext()
+                       renderTemplate(loadedHtmlPageTemplate) { templateContext ->
                                templateContext["link"] = linkedElement.link
                                templateContext["title"] = linkedElement.properties["title"] ?: "No title"
                                templateContext["description"] = linkedElement.properties["description"] ?: "No description"
-                               it.also { loadedHtmlPageTemplate.render(templateContext, it) }
-                       }.toString()
+                       }
+
+       private fun renderAudioPlayer(linkedElement: LinkedElement) =
+                       renderTemplate(loadedAudioTemplate) { templateContext ->
+                               templateContext["link"] = linkedElement.link
+                       }
 
        private fun renderNotLoadedLinkedElement(linkedElement: LinkedElement) =
-                       StringWriter().use {
-                               val templateContext = templateContextFactory.createTemplateContext()
+                       renderTemplate(notLoadedImageTemplate) { templateContext ->
                                templateContext["link"] = linkedElement.link
-                               it.also { notLoadedImageTemplate.render(templateContext, it) }
+                       }
+
+       private fun renderTemplate(template: Template, fillTemplateContext: (TemplateContext) -> Unit) =
+                       StringWriter().use { stringWriter ->
+                               stringWriter.also {
+                                       templateContextFactory.createTemplateContext().also { templateContext ->
+                                               fillTemplateContext(templateContext)
+                                               template.render(templateContext, stringWriter)
+                                       }
+                               }
                        }.toString()
 
 }
 
 private val loadedImageTemplate = """<%include linked/image.html>""".asTemplate()
 private val loadedHtmlPageTemplate = """<%include linked/html-page.html>""".asTemplate()
+private val loadedAudioTemplate = """<%include linked/audio.html>""".asTemplate()
 private val notLoadedImageTemplate = """<%include linked/notLoaded.html>""".asTemplate()
diff --git a/src/main/resources/templates/linked/audio.html b/src/main/resources/templates/linked/audio.html
new file mode 100644 (file)
index 0000000..cd04c5b
--- /dev/null
@@ -0,0 +1 @@
+<span class="linked-element loaded" title="<%link|html>"><audio controls src="/<% link|html>"></audio></span>
index c66e6ff..fb9217e 100644 (file)
@@ -7,13 +7,13 @@ import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback
 import net.pterodactylus.sone.test.*
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
-import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import java.io.ByteArrayOutputStream
 import java.util.concurrent.TimeUnit
+import kotlin.test.Test
 
 /**
  * Unit test for [DefaultElementLoaderTest].
@@ -97,6 +97,17 @@ class DefaultElementLoaderTest {
        }
 
        @Test
+       fun `image loader returns element for audio data`() {
+               elementLoader.loadElement(audioKey)
+               verify(freenetInterface).startFetch(eq(FreenetURI(audioKey)), callback.capture())
+               callback.value.shouldCancel(FreenetURI(audioKey), "audio/mpeg", 123)
+               val linkedElement = elementLoader.loadElement(audioKey)
+               assertThat(linkedElement, equalTo(LinkedElement(audioKey, properties = mapOf(
+                               "type" to "audio", "size" to 123L, "sizeHuman" to "123 B"
+               ))))
+       }
+
+       @Test
        fun `element loader can extract description from description header`() {
                elementLoader.loadElement(textKey)
                verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
@@ -190,6 +201,7 @@ class DefaultElementLoaderTest {
 private const val IMAGE_ID = "KSK@gpl.png"
 private val freenetURI = FreenetURI(IMAGE_ID)
 private const val decomposedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/fru%CC%88hstu%CC%88ck.jpg"
+private const val audioKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/audio.mp3"
 private const val normalizedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/frühstück.jpg"
 private const val textKey = "KSK@gpl.html"
 private const val sizeOkay = 2097152L
index 8363f6b..e93bfeb 100644 (file)
@@ -8,7 +8,7 @@ import org.hamcrest.MatcherAssert.*
 import org.hamcrest.Matchers.*
 import org.jsoup.*
 import org.jsoup.nodes.*
-import org.junit.*
+import kotlin.test.Test
 
 /**
  * Unit test for [LinkedElementRenderFilter].
@@ -64,6 +64,19 @@ class LinkedElementRenderFilterTest {
        }
 
        @Test
+       fun `filter can render audio player`() {
+               val html = filter.format(null, LinkedElement("KSK@gpl.mp3", properties = mapOf("type" to "audio")), emptyMap()) as String
+               val outerSpanNode = Jsoup.parseBodyFragment(html).body().child(0)
+               assertThat(outerSpanNode.nodeName(), equalTo("span"))
+               assertThat(outerSpanNode.attr("class"), equalTo("linked-element loaded"))
+               assertThat(outerSpanNode.attr("title"), equalTo("KSK@gpl.mp3"))
+               val linkNode = outerSpanNode.child(0)
+               assertThat(linkNode.nodeName(), equalTo("audio"))
+               assertThat(linkNode.attr("controls"), equalTo(""))
+               assertThat(linkNode.attr("src"), equalTo("/KSK@gpl.mp3"))
+       }
+
+       @Test
        fun `render filter can be created by guice`() {
                val injector = Guice.createInjector(TemplateContextFactory::class.isProvidedByMock())
                assertThat(injector.getInstance<LinkedElementRenderFilter>(), notNullValue())