diff --git a/komga/build.gradle.kts b/komga/build.gradle.kts index 815503c1e..34d39abda 100644 --- a/komga/build.gradle.kts +++ b/komga/build.gradle.kts @@ -56,6 +56,8 @@ dependencies { implementation("org.apache.tika:tika-core:1.22") implementation("net.lingala.zip4j:zip4j:2.1.2") + implementation("com.github.junrar:junrar:4.0.0") + implementation("net.grey-panther:natural-comparator:1.1") runtimeOnly("com.h2database:h2:1.4.199") diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt index 3fc1c7602..cb8b0b12f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt @@ -6,6 +6,7 @@ import org.gotson.komga.domain.model.BookMetadata import org.gotson.komga.domain.model.Status import org.gotson.komga.domain.model.path import org.gotson.komga.infrastructure.archive.ContentDetector +import org.gotson.komga.infrastructure.archive.RarExtractor import org.gotson.komga.infrastructure.archive.ZipExtractor import org.springframework.stereotype.Service import java.io.InputStream @@ -15,11 +16,13 @@ private val logger = KotlinLogging.logger {} @Service class BookParser( private val contentDetector: ContentDetector, - private val zipExtractor: ZipExtractor + private val zipExtractor: ZipExtractor, + private val rarExtractor: RarExtractor ) { - val supportedMimeTypes = listOf( - "application/zip" + val supportedMediaTypes = mapOf( + "application/zip" to zipExtractor, + "application/x-rar-compressed" to rarExtractor ) fun parse(book: Book): BookMetadata { @@ -27,10 +30,10 @@ class BookParser( val mediaType = contentDetector.detectMediaType(book.path()) logger.info { "Detected media type: $mediaType" } - if (!supportedMimeTypes.contains(mediaType)) + if (!supportedMediaTypes.keys.contains(mediaType)) throw UnsupportedMediaTypeException("Unsupported mime type: $mediaType. File: ${book.url}", mediaType) - val pageNames = zipExtractor.getFilenames(book.path()) + val pageNames = supportedMediaTypes.getValue(mediaType).getFilenames(book.path()) logger.info { "Book has ${pageNames.size} pages" } return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pageNames) @@ -49,7 +52,7 @@ class BookParser( throw ArrayIndexOutOfBoundsException("Page $number does not exist") } - return zipExtractor.getEntryStream(book.path(), book.metadata.pages[number - 1]) + return supportedMediaTypes.getValue(book.metadata.mediaType!!).getEntryStream(book.path(), book.metadata.pages[number - 1]) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt index 507dd7322..682cd0cd0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt @@ -20,7 +20,7 @@ private val logger = KotlinLogging.logger {} @Service class FileSystemScanner { - val supportedExtensions = listOf("cbz", "zip") + val supportedExtensions = listOf("cbz", "zip", "cbr", "rar") fun scanRootFolder(root: Path): List { logger.info { "Scanning folder: $root" } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ArchiveExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ArchiveExtractor.kt new file mode 100644 index 000000000..ca26cf6a5 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ArchiveExtractor.kt @@ -0,0 +1,14 @@ +package org.gotson.komga.infrastructure.archive + +import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator +import java.io.InputStream +import java.nio.file.Path +import java.util.* + +abstract class ArchiveExtractor { + + protected val natSortComparator: Comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() + + abstract fun getFilenames(path: Path): List + abstract fun getEntryStream(path: Path, entryName: String): InputStream +} \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/RarExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/RarExtractor.kt new file mode 100644 index 000000000..d5ed6437a --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/RarExtractor.kt @@ -0,0 +1,24 @@ +package org.gotson.komga.infrastructure.archive + +import com.github.junrar.Archive +import com.github.junrar.Junrar +import org.springframework.stereotype.Service +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path + +@Service +class RarExtractor : ArchiveExtractor() { + + override fun getFilenames(path: Path): List { + val contentsDescription = Junrar.getContentsDescription(path.toFile()) + + return contentsDescription.map { it.path }.sortedWith(natSortComparator) + } + + override fun getEntryStream(path: Path, entryName: String): InputStream { + val archive = Archive(Files.newInputStream(path)) + val header = archive.fileHeaders.find { it.fileNameString == entryName } + return archive.getInputStream(header) + } +} \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ZipExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ZipExtractor.kt index 10f89d9fd..3f70fb9a6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ZipExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/archive/ZipExtractor.kt @@ -6,12 +6,12 @@ import java.io.InputStream import java.nio.file.Path @Service -class ZipExtractor { +class ZipExtractor : ArchiveExtractor() { - fun getFilenames(path: Path) = - ZipFile(path.toFile()).fileHeaders.map { it.fileName }.toMutableList() + override fun getFilenames(path: Path) = + ZipFile(path.toFile()).fileHeaders.map { it.fileName }.sortedWith(natSortComparator) - fun getEntryStream(path: Path, entryName: String): InputStream = + override fun getEntryStream(path: Path, entryName: String): InputStream = ZipFile(path.toFile()).let { it.getInputStream(it.getFileHeader(entryName)) }