diff --git a/.gitignore b/.gitignore index 81784efe3d..b125222dd8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,10 @@ node_modules ### Logs *.log *.log.*.gz +*.log.*.tmp + +### Home-dir, TODO: use user.home var +~ ### H2 databases *.mv.db diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt index 0cca3013cd..8c3c3d9222 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import mu.KotlinLogging +import org.apache.commons.io.FilenameUtils import org.gotson.komga.application.tasks.TaskReceiver import org.gotson.komga.domain.model.Author import org.gotson.komga.domain.model.BookSearchWithReadProgress @@ -52,14 +53,19 @@ import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.context.request.WebRequest import org.springframework.web.server.ResponseStatusException +import java.io.File import java.io.FileNotFoundException +import java.nio.file.Files import java.nio.file.NoSuchFileException +import java.nio.file.Path import java.time.ZoneOffset import java.util.concurrent.TimeUnit import javax.validation.Valid private val logger = KotlinLogging.logger {} +internal val COVER_EXTS = arrayOf("jpg", "jpeg", "png") + @RestController @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) class BookController( @@ -193,6 +199,23 @@ class BookController( if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + bookRepository.findByIdOrNull(bookId)?.let { book -> + val fileExtension = FilenameUtils.getExtension(book.fileName()) + val filePath = book.path().toString() + val thumbPrefix = filePath.substring(0, filePath.lastIndexOf(".$fileExtension")) + + val thumbExts = arrayOf("jpg", "png", "jpeg") + for (ext in thumbExts) { + val thumbFile = File("$thumbPrefix.$ext") + if (thumbFile.exists()) { + val fileBytes = thumbFile.toURI().toURL().readBytes() + return ResponseEntity.ok() + .setCachePrivate() + .body(fileBytes) + } + } + } + return mediaRepository.getThumbnail(bookId)?.let { ResponseEntity.ok() .setCachePrivate() diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt index bd1b96aa47..0bab5470bd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import mu.KotlinLogging +import org.apache.commons.io.FilenameUtils import org.gotson.komga.application.tasks.TaskReceiver import org.gotson.komga.domain.model.BookSearchWithReadProgress import org.gotson.komga.domain.model.Media @@ -25,10 +26,12 @@ import org.gotson.komga.interfaces.rest.dto.SeriesMetadataUpdateDto import org.gotson.komga.interfaces.rest.dto.restrictUrl import org.gotson.komga.interfaces.rest.persistence.BookDtoRepository import org.gotson.komga.interfaces.rest.persistence.SeriesDtoRepository +import org.springframework.core.io.FileSystemResource import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort +import org.springframework.http.CacheControl import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity @@ -45,6 +48,10 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit import javax.validation.Valid private val logger = KotlinLogging.logger {} @@ -177,7 +184,31 @@ class SeriesController( } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) return bookRepository.findFirstIdInSeries(seriesId)?.let { - bookController.getBookThumbnail(principal, it) + bookRepository.findByIdOrNull(it)?.let { firstBook -> + var dirPath = firstBook.path().parent.toString() + if (dirPath.endsWith(File.separatorChar)) { + dirPath = dirPath.substring(0, dirPath.length - 1) + } + + var response: ResponseEntity? = null + + for (ext in COVER_EXTS) { + val thumbFile = FileSystemResource(File("$dirPath.$ext").toPath()) + if (thumbFile.exists()) { + val fileBytes = thumbFile.uri.toURL().readBytes() + response = ResponseEntity.ok() + .setCachePrivate() + .body(fileBytes) + break + } + } + + if (response == null) { + response = bookController.getBookThumbnail(principal, it) + } + + return response + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } @@ -282,4 +313,10 @@ class SeriesController( bookLifecycle.deleteReadProgress(it, principal.user) } } + + private fun ResponseEntity.BodyBuilder.setCachePrivate() = + this.cacheControl(CacheControl.maxAge(0, TimeUnit.SECONDS) + .cachePrivate() + .mustRevalidate() + ) }