perf: page streaming performance

cache the zip or pdf document to avoid recreating it at every page access
This commit is contained in:
Gauthier Roebroeck 2021-05-24 16:13:54 +08:00
parent 02f61bac0a
commit 8de01a6fd7
2 changed files with 29 additions and 13 deletions

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.mediacontainer package org.gotson.komga.infrastructure.mediacontainer
import com.github.benmanes.caffeine.cache.Caffeine
import mu.KotlinLogging import mu.KotlinLogging
import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.pdmodel.PDPage import org.apache.pdfbox.pdmodel.PDPage
@ -11,6 +12,7 @@ import org.gotson.komga.infrastructure.image.ImageAnalyzer
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -25,6 +27,12 @@ class PdfExtractor(
private val imageIOFormat = "jpeg" private val imageIOFormat = "jpeg"
private val resolution = 1536F private val resolution = 1536F
private val cache = Caffeine.newBuilder()
.maximumSize(20)
.expireAfterAccess(1, TimeUnit.MINUTES)
.removalListener { _: Path?, pdf: PDDocument?, _ -> pdf?.close() }
.build<Path, PDDocument>()
override fun mediaTypes(): List<String> = listOf("application/pdf") override fun mediaTypes(): List<String> = listOf("application/pdf")
override fun getEntries(path: Path): List<MediaContainerEntry> = override fun getEntries(path: Path): List<MediaContainerEntry> =
@ -37,16 +45,16 @@ class PdfExtractor(
} }
} }
override fun getEntryStream(path: Path, entryName: String): ByteArray = override fun getEntryStream(path: Path, entryName: String): ByteArray {
PDDocument.load(path.toFile()).use { pdf -> val pdf = cache.get(path) { PDDocument.load(path.toFile()) }!!
val pageNumber = entryName.toInt() val pageNumber = entryName.toInt()
val page = pdf.getPage(pageNumber) val page = pdf.getPage(pageNumber)
val image = PDFRenderer(pdf).renderImage(pageNumber, page.getScale(), ImageType.RGB) val image = PDFRenderer(pdf).renderImage(pageNumber, page.getScale(), ImageType.RGB)
ByteArrayOutputStream().use { out -> return ByteArrayOutputStream().use { out ->
ImageIO.write(image, imageIOFormat, out) ImageIO.write(image, imageIOFormat, out)
out.toByteArray() out.toByteArray()
}
} }
}
private fun PDPage.getScale() = resolution / minOf(cropBox.width, cropBox.height) private fun PDPage.getScale() = resolution / minOf(cropBox.width, cropBox.height)
} }

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.mediacontainer package org.gotson.komga.infrastructure.mediacontainer
import com.github.benmanes.caffeine.cache.Caffeine
import mu.KotlinLogging import mu.KotlinLogging
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import org.apache.commons.compress.archivers.zip.ZipFile import org.apache.commons.compress.archivers.zip.ZipFile
@ -7,6 +8,7 @@ import org.gotson.komga.domain.model.MediaContainerEntry
import org.gotson.komga.infrastructure.image.ImageAnalyzer import org.gotson.komga.infrastructure.image.ImageAnalyzer
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.TimeUnit
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
@ -16,6 +18,12 @@ class ZipExtractor(
private val imageAnalyzer: ImageAnalyzer private val imageAnalyzer: ImageAnalyzer
) : MediaContainerExtractor { ) : MediaContainerExtractor {
private val cache = Caffeine.newBuilder()
.maximumSize(20)
.expireAfterAccess(1, TimeUnit.MINUTES)
.removalListener { _: Path?, zip: ZipFile?, _ -> zip?.close() }
.build<Path, ZipFile>()
private val natSortComparator: Comparator<String> = CaseInsensitiveSimpleNaturalComparator.getInstance() private val natSortComparator: Comparator<String> = CaseInsensitiveSimpleNaturalComparator.getInstance()
override fun mediaTypes(): List<String> = listOf("application/zip") override fun mediaTypes(): List<String> = listOf("application/zip")
@ -42,8 +50,8 @@ class ZipExtractor(
.sortedWith(compareBy(natSortComparator) { it.name }) .sortedWith(compareBy(natSortComparator) { it.name })
} }
override fun getEntryStream(path: Path, entryName: String): ByteArray = override fun getEntryStream(path: Path, entryName: String): ByteArray {
ZipFile(path.toFile()).use { zip -> val zip = cache.get(path) { ZipFile(path.toFile()) }!!
zip.getInputStream(zip.getEntry(entryName)).use { it.readBytes() } return zip.getInputStream(zip.getEntry(entryName)).use { it.readBytes() }
} }
} }