Skip to content

Commit

Permalink
feat: Add custom cover backend to controllers
Browse files Browse the repository at this point in the history
This helps answer gotson#63.

To change a series cover, one adds an image (jpg/jpeg/png) file with the
same directory name prefix, i.e:
- One Piece
- One Piece.jpg

To change a chapter cover, one needs to name it the same way as a chapter
file, changing the extension to (jpg/jpeg/png), i.e:
- One Piece
  - Chapter 1.cbz
  - Chapter 1.jpg
  • Loading branch information
TSedlar committed Jun 10, 2020
1 parent 327ed00 commit 78b215e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ node_modules
### Logs
*.log
*.log.*.gz
*.log.*.tmp

### Home-dir, TODO: use user.home var
~

### H2 databases
*.mv.db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {}
Expand Down Expand Up @@ -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<ByteArray>? = 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)
}

Expand Down Expand Up @@ -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()
)
}

0 comments on commit 78b215e

Please sign in to comment.