diff --git a/komga-webui/src/components/BrowseBook.vue b/komga-webui/src/components/BrowseBook.vue
index b811d12cf..dfe2b82a1 100644
--- a/komga-webui/src/components/BrowseBook.vue
+++ b/komga-webui/src/components/BrowseBook.vue
@@ -84,16 +84,17 @@
{{ book.size }}
+
+ COMMENT
+
+ {{ book.media.comment }}
+
+
+
FORMAT
-
- Book analysis failed
-
-
- File type not supported: {{ book.media.mediaType }}
-
- {{ format.type }}
+ {{ format.type }}
diff --git a/komga-webui/src/types/komga-books.ts b/komga-webui/src/types/komga-books.ts
index f0a32aad8..1e8cf976f 100644
--- a/komga-webui/src/types/komga-books.ts
+++ b/komga-webui/src/types/komga-books.ts
@@ -13,7 +13,8 @@ interface BookDto {
interface MediaDto {
status: string,
mediaType: string,
- pagesCount: number
+ pagesCount: number,
+ comment: string
}
interface PageDto {
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt
index 04c091800..8e5dbaa5e 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt
@@ -1,8 +1,7 @@
package org.gotson.komga.domain.model
class MediaNotReadyException : Exception()
-class EmptyBookException(val mediaType: String) : Exception()
-class UnsupportedMediaTypeException(message: String, val mediaType: String) : Exception(message)
+class ImageConversionException(message: String) : Exception(message)
class DirectoryNotFoundException(message: String) : Exception(message)
class DuplicateNameException(message: String) : Exception(message)
class PathContainedInPath(message: String) : Exception(message)
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt
index f5442e94d..3025d5613 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt
@@ -37,7 +37,10 @@ class Media(
@Lob
var thumbnail: ByteArray? = null,
- pages: Iterable = emptyList()
+ pages: Iterable = emptyList(),
+
+ @Column(name = "comment")
+ var comment: String? = null
) : AuditableEntity() {
@Id
@GeneratedValue
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt
index 2b2e67993..834c299fc 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt
@@ -4,10 +4,8 @@ import mu.KotlinLogging
import net.coobird.thumbnailator.Thumbnails
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import org.gotson.komga.domain.model.Book
-import org.gotson.komga.domain.model.EmptyBookException
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
-import org.gotson.komga.domain.model.UnsupportedMediaTypeException
import org.gotson.komga.infrastructure.archive.ContentDetector
import org.gotson.komga.infrastructure.archive.PdfExtractor
import org.gotson.komga.infrastructure.archive.RarExtractor
@@ -37,21 +35,25 @@ class BookAnalyzer(
private val thumbnailSize = 300
private val thumbnailFormat = "jpeg"
- @Throws(
- UnsupportedMediaTypeException::class,
- EmptyBookException::class
- )
fun analyze(book: Book): Media {
logger.info { "Trying to analyze book: $book" }
val mediaType = contentDetector.detectMediaType(book.path())
logger.info { "Detected media type: $mediaType" }
if (!supportedMediaTypes.keys.contains(mediaType))
- throw UnsupportedMediaTypeException("Unsupported mime type: $mediaType. File: $book", mediaType)
+ return Media(mediaType = mediaType, status = Media.Status.UNSUPPORTED, comment = "Media type $mediaType is not supported")
- val pages = supportedMediaTypes.getValue(mediaType).getPagesList(book.path())
- .sortedWith(compareBy(natSortComparator) { it.fileName })
- if (pages.isEmpty()) throw EmptyBookException(mediaType)
+ val pages = try {
+ supportedMediaTypes.getValue(mediaType).getPagesList(book.path()).sortedWith(compareBy(natSortComparator) { it.fileName })
+ } catch (ex: Exception) {
+ logger.error(ex) { "Error while analyzing book: $book" }
+ return Media(mediaType = mediaType, status = Media.Status.ERROR, comment = ex.message)
+ }
+
+ if (pages.isEmpty()) {
+ logger.warn { "Book $book does not contain any pages" }
+ return Media(mediaType = mediaType, status = Media.Status.ERROR, comment = "Book does not contain any pages")
+ }
logger.info { "Book has ${pages.size} pages" }
logger.info { "Trying to generate cover for book: $book" }
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt
index 6a6df2728..1818aebfc 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt
@@ -4,10 +4,9 @@ import mu.KotlinLogging
import org.apache.commons.lang3.time.DurationFormatUtils
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookPageContent
-import org.gotson.komga.domain.model.EmptyBookException
+import org.gotson.komga.domain.model.ImageConversionException
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
-import org.gotson.komga.domain.model.UnsupportedMediaTypeException
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.infrastructure.image.ImageConverter
import org.gotson.komga.infrastructure.image.ImageType
@@ -34,15 +33,9 @@ class BookLifecycle(
return AsyncResult(measureTimeMillis {
try {
book.media = bookAnalyzer.analyze(book)
- } catch (ex: UnsupportedMediaTypeException) {
- logger.warn { "Unsupported media type: ${ex.mediaType}. Book: $book" }
- book.media = Media(status = Media.Status.UNSUPPORTED, mediaType = ex.mediaType)
- } catch (ex: EmptyBookException) {
- logger.warn { "Book does not contain any images: $book" }
- book.media = Media(status = Media.Status.ERROR, mediaType = ex.mediaType)
} catch (ex: Exception) {
- logger.error(ex) { "Error while parsing. Book: $book" }
- book.media = Media(status = Media.Status.ERROR)
+ logger.error(ex) { "Error while analyzing book: $book" }
+ book.media = Media(status = Media.Status.ERROR, comment = ex.message)
}
bookRepository.save(book)
}.also { logger.info { "Parsing finished in ${DurationFormatUtils.formatDurationHMS(it)}" } })
@@ -64,9 +57,9 @@ class BookLifecycle(
}
@Throws(
- UnsupportedMediaTypeException::class,
- MediaNotReadyException::class,
- IndexOutOfBoundsException::class
+ ImageConversionException::class,
+ MediaNotReadyException::class,
+ IndexOutOfBoundsException::class
)
fun getBookPage(book: Book, number: Int, convertTo: ImageType? = null): BookPageContent {
val pageContent = bookAnalyzer.getPageContent(book, number)
@@ -75,10 +68,10 @@ class BookLifecycle(
convertTo?.let {
val msg = "Convert page #$number of book $book from $pageMediaType to ${it.mediaType}"
if (!imageConverter.supportedReadMediaTypes.contains(pageMediaType)) {
- throw UnsupportedMediaTypeException("$msg: unsupported read format $pageMediaType", pageMediaType)
+ throw ImageConversionException("$msg: unsupported read format $pageMediaType")
}
if (!imageConverter.supportedWriteMediaTypes.contains(it.mediaType)) {
- throw UnsupportedMediaTypeException("$msg: unsupported cannot write format ${it.mediaType}", it.mediaType)
+ throw ImageConversionException("$msg: unsupported write format ${it.mediaType}")
}
if (pageMediaType == it.mediaType) {
logger.warn { "$msg: same format, no need for conversion" }
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 b2a1c0afb..43753a4c3 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
@@ -4,6 +4,7 @@ import com.github.klinq.jpaspec.`in`
import com.github.klinq.jpaspec.likeLower
import mu.KotlinLogging
import org.gotson.komga.domain.model.Book
+import org.gotson.komga.domain.model.ImageConversionException
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
import org.gotson.komga.domain.persistence.BookRepository
@@ -227,6 +228,8 @@ class BookController(
.body(pageContent.content)
} catch (ex: IndexOutOfBoundsException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist")
+ } catch (ex: ImageConversionException) {
+ throw ResponseStatusException(HttpStatus.NOT_FOUND, ex.message)
} catch (ex: MediaNotReadyException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed")
} catch (ex: NoSuchFileException) {
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/Dto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/Dto.kt
index d1fd3e2bf..f0d58bf04 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/Dto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/Dto.kt
@@ -9,84 +9,87 @@ import java.time.ZoneId
import java.time.ZoneOffset
data class SeriesDto(
- val id: Long,
- val libraryId: Long,
- val name: String,
- val url: String,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val created: LocalDateTime?,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val lastModified: LocalDateTime?,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val fileLastModified: LocalDateTime,
- val booksCount: Int
+ val id: Long,
+ val libraryId: Long,
+ val name: String,
+ val url: String,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val created: LocalDateTime?,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val lastModified: LocalDateTime?,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val fileLastModified: LocalDateTime,
+ val booksCount: Int
)
fun Series.toDto(includeUrl: Boolean) = SeriesDto(
- id = id,
- libraryId = library.id,
- name = name,
- url = if (includeUrl) url.toURI().path else "",
- created = createdDate?.toUTC(),
- lastModified = lastModifiedDate?.toUTC(),
- fileLastModified = fileLastModified.toUTC(),
- booksCount = books.size
+ id = id,
+ libraryId = library.id,
+ name = name,
+ url = if (includeUrl) url.toURI().path else "",
+ created = createdDate?.toUTC(),
+ lastModified = lastModifiedDate?.toUTC(),
+ fileLastModified = fileLastModified.toUTC(),
+ booksCount = books.size
)
data class BookDto(
- val id: Long,
- val seriesId: Long,
- val name: String,
- val url: String,
- val number: Float,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val created: LocalDateTime?,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val lastModified: LocalDateTime?,
- @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
- val fileLastModified: LocalDateTime,
- val sizeBytes: Long,
- val size: String,
- @Deprecated("Deprecated since 0.10", ReplaceWith("media"))
- val metadata: MediaDto,
- val media: MediaDto
+ val id: Long,
+ val seriesId: Long,
+ val name: String,
+ val url: String,
+ val number: Float,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val created: LocalDateTime?,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val lastModified: LocalDateTime?,
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ val fileLastModified: LocalDateTime,
+ val sizeBytes: Long,
+ val size: String,
+ @Deprecated("Deprecated since 0.10", ReplaceWith("media"))
+ val metadata: MediaDto,
+ val media: MediaDto
)
data class MediaDto(
- val status: String,
- val mediaType: String,
- val pagesCount: Int
+ val status: String,
+ val mediaType: String,
+ val pagesCount: Int,
+ val comment: String
)
fun Book.toDto(includeFullUrl: Boolean) =
- BookDto(
- id = id,
- seriesId = series.id,
- name = name,
- url = if (includeFullUrl) url.toURI().path else FilenameUtils.getName(url.toURI().path),
- number = number,
- created = createdDate?.toUTC(),
- lastModified = lastModifiedDate?.toUTC(),
- fileLastModified = fileLastModified.toUTC(),
- sizeBytes = fileSize,
- size = fileSizeHumanReadable(),
- metadata = MediaDto(
- status = media.status.toString(),
- mediaType = media.mediaType ?: "",
- pagesCount = media.pages.size
- ),
- media = MediaDto(
- status = media.status.toString(),
- mediaType = media.mediaType ?: "",
- pagesCount = media.pages.size
- )
+ BookDto(
+ id = id,
+ seriesId = series.id,
+ name = name,
+ url = if (includeFullUrl) url.toURI().path else FilenameUtils.getName(url.toURI().path),
+ number = number,
+ created = createdDate?.toUTC(),
+ lastModified = lastModifiedDate?.toUTC(),
+ fileLastModified = fileLastModified.toUTC(),
+ sizeBytes = fileSize,
+ size = fileSizeHumanReadable(),
+ metadata = MediaDto(
+ status = media.status.toString(),
+ mediaType = media.mediaType ?: "",
+ pagesCount = media.pages.size,
+ comment = media.comment ?: ""
+ ),
+ media = MediaDto(
+ status = media.status.toString(),
+ mediaType = media.mediaType ?: "",
+ pagesCount = media.pages.size,
+ comment = media.comment ?: ""
)
+ )
data class PageDto(
- val number: Int,
- val fileName: String,
- val mediaType: String
+ val number: Int,
+ val fileName: String,
+ val mediaType: String
)
fun LocalDateTime.toUTC(): LocalDateTime =
- atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
+ atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
diff --git a/komga/src/main/resources/db/migration/V20200108103325__media_comment.sql b/komga/src/main/resources/db/migration/V20200108103325__media_comment.sql
new file mode 100644
index 000000000..a1f9b8ef6
--- /dev/null
+++ b/komga/src/main/resources/db/migration/V20200108103325__media_comment.sql
@@ -0,0 +1,2 @@
+alter table media
+ add (comment varchar);