diff --git a/komga-webui/src/components/dialogs/LibraryEditDialog.vue b/komga-webui/src/components/dialogs/LibraryEditDialog.vue
index 38100e379..0a4f9b7e1 100644
--- a/komga-webui/src/components/dialogs/LibraryEditDialog.vue
+++ b/komga-webui/src/components/dialogs/LibraryEditDialog.vue
@@ -92,12 +92,23 @@
hide-details
class="mx-4"
/>
+
+ >
+
+
+
+ mdi-help-circle-outline
+
+ {{ $t('dialog.edit_library.tooltip_scanner_force_modified_time') }}
+
+
+
+
+
+
+ {{ $t('dialog.edit_library.label_analysis') }}
+
+
+
+
+ mdi-alert-circle-outline
+
+ {{ $t('dialog.edit_library.tooltip_use_resources') }}
+
+
+
+
+
+
+
+
+ mdi-alert-circle-outline
+
+ {{ $t('dialog.edit_library.tooltip_use_resources') }}
+
+
+
+
+
+
+
+
+ mdi-alert-circle-outline
+
+ {{ $t('dialog.edit_library.tooltip_use_resources') }}
+
+
+
+
+
- {{ $t('dialog.edit_library.label_series_cover') }}
+ {{
+ $t('dialog.edit_library.label_series_cover')
+ }}
- {{ $t('dialog.edit_library.label_import_mylar') }}
+ {{
+ $t('dialog.edit_library.label_import_mylar')
+ }}
+ >
+
+
+
+ mdi-alert-circle-outline
+
+ {{ $t('dialog.edit_library.tooltip_use_resources') }}
+
+
+
@@ -307,13 +383,16 @@ export default Vue.extend({
importEpubSeries: true,
importMylarSeries: true,
importLocalArtwork: true,
- importBarcodeIsbn: true,
+ importBarcodeIsbn: false,
scanForceModifiedTime: false,
scanDeep: false,
repairExtensions: false,
convertToCbz: false,
emptyTrashAfterScan: false,
seriesCover: SeriesCoverDto.FIRST as SeriesCoverDto,
+ hashFiles: true,
+ hashPages: false,
+ analyzeDimensions: true,
},
validationFieldNames: new Map([]),
}
@@ -435,13 +514,16 @@ export default Vue.extend({
this.form.importEpubSeries = library ? library.importEpubSeries : true
this.form.importMylarSeries = library ? library.importMylarSeries : true
this.form.importLocalArtwork = library ? library.importLocalArtwork : true
- this.form.importBarcodeIsbn = library ? library.importBarcodeIsbn : true
+ this.form.importBarcodeIsbn = library ? library.importBarcodeIsbn : false
this.form.scanForceModifiedTime = library ? library.scanForceModifiedTime : false
this.form.scanDeep = library ? library.scanDeep : false
this.form.repairExtensions = library ? library.repairExtensions : false
this.form.convertToCbz = library ? library.convertToCbz : false
this.form.emptyTrashAfterScan = library ? library.emptyTrashAfterScan : false
this.form.seriesCover = library ? library.seriesCover : SeriesCoverDto.FIRST
+ this.form.hashFiles = library ? library.hashFiles : true
+ this.form.hashPages = library ? library.hashPages : false
+ this.form.analyzeDimensions = library ? library.analyzeDimensions : true
this.$v.$reset()
},
validateLibrary() {
@@ -466,6 +548,9 @@ export default Vue.extend({
convertToCbz: this.form.convertToCbz,
emptyTrashAfterScan: this.form.emptyTrashAfterScan,
seriesCover: this.form.seriesCover,
+ hashFiles: this.form.hashFiles,
+ hashPages: this.form.hashPages,
+ analyzeDimensions: this.form.analyzeDimensions,
}
}
return null
diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json
index c26e1f5b2..871bf41a1 100644
--- a/komga-webui/src/locales/en.json
+++ b/komga-webui/src/locales/en.json
@@ -363,6 +363,9 @@
"button_next": "Next",
"dialog_title_add": "Add Library",
"dialot_title_edit": "Edit Library",
+ "field_analysis_analyze_dimensions": "Analyze pages dimensions",
+ "field_analysis_hash_files": "Compute hash for files",
+ "field_analysis_hash_pages": "Compute hash for pages",
"field_convert_to_cbz": "Automatically convert to CBZ",
"field_import_barcode_isbn": "ISBN barcode",
"field_import_comicinfo_book": "Book metadata",
@@ -382,6 +385,7 @@
"field_series_cover": "Series cover",
"file_browser_dialog_button_confirm": "Choose",
"file_browser_dialog_title": "Library's root folder",
+ "label_analysis": "Analysis",
"label_file_management": "File management",
"label_import_barcode_isbn": "Import ISBN within barcode",
"label_import_comicinfo": "Import metadata for CBR/CBZ containing a ComicInfo.xml file",
@@ -392,7 +396,9 @@
"label_series_cover": "Series cover",
"tab_general": "General",
"tab_metadata": "Metadata",
- "tab_options": "Options"
+ "tab_options": "Options",
+ "tooltip_scanner_force_modified_time": "Enable if the library is on a Google Drive",
+ "tooltip_use_resources": "Can consume lots of resources on large libraries or slow hardware"
},
"edit_readlist": {
"button_cancel": "Cancel",
diff --git a/komga-webui/src/types/komga-libraries.ts b/komga-webui/src/types/komga-libraries.ts
index 32334c53b..e36dc4524 100644
--- a/komga-webui/src/types/komga-libraries.ts
+++ b/komga-webui/src/types/komga-libraries.ts
@@ -18,6 +18,9 @@ export interface LibraryCreationDto {
convertToCbz: boolean,
emptyTrashAfterScan: boolean,
seriesCover: SeriesCoverDto,
+ hashFiles: boolean,
+ hashPages: boolean,
+ analyzeDimensions: boolean,
}
export interface LibraryUpdateDto {
@@ -38,6 +41,9 @@ export interface LibraryUpdateDto {
convertToCbz: boolean,
emptyTrashAfterScan: boolean,
seriesCover: SeriesCoverDto,
+ hashFiles: boolean,
+ hashPages: boolean,
+ analyzeDimensions: boolean,
}
export interface LibraryDto {
@@ -59,5 +65,8 @@ export interface LibraryDto {
convertToCbz: boolean,
emptyTrashAfterScan: boolean,
seriesCover: SeriesCoverDto,
+ hashFiles: boolean,
+ hashPages: boolean,
+ analyzeDimensions: boolean,
unavailable: boolean,
}
diff --git a/komga/build.gradle.kts b/komga/build.gradle.kts
index 1597708dd..5f9c6e7da 100644
--- a/komga/build.gradle.kts
+++ b/komga/build.gradle.kts
@@ -228,6 +228,7 @@ val migrationDirsSqlite = listOf(
flyway {
url = dbSqlite["url"]
locations = arrayOf("classpath:db/migration/sqlite")
+ placeholders = mapOf("library-file-hashing" to "true")
}
tasks.flywayMigrate {
// in order to include the Java migrations, flywayClasses must be run before flywayMigrate
diff --git a/komga/src/flyway/resources/db/migration/sqlite/V20220105143154__library_hashing_options.sql b/komga/src/flyway/resources/db/migration/sqlite/V20220105143154__library_hashing_options.sql
new file mode 100644
index 000000000..14bb55a4d
--- /dev/null
+++ b/komga/src/flyway/resources/db/migration/sqlite/V20220105143154__library_hashing_options.sql
@@ -0,0 +1,6 @@
+alter table library
+ add column HASH_FILES boolean NOT NULL DEFAULT ${library-file-hashing};
+alter table library
+ add column HASH_PAGES boolean NOT NULL DEFAULT 0;
+alter table library
+ add column ANALYZE_DIMENSIONS boolean NOT NULL DEFAULT 1;
diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt
index cfe893000..4ecc70dc1 100644
--- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt
@@ -66,16 +66,17 @@ class TaskReceiver(
}
fun hashBooksWithoutHash(library: Library) {
- if (komgaProperties.fileHashing)
+ if (library.hashFiles)
bookRepository.findAllIdsByLibraryIdAndWithEmptyHash(library.id).forEach {
submitTask(Task.HashBook(it, LOWEST_PRIORITY))
}
}
fun hashBookPagesWithMissingHash(library: Library) {
- mediaRepository.findAllBookIdsByLibraryIdAndWithMissingPageHash(library.id, komgaProperties.pageHashing).forEach {
- submitTask(Task.HashBookPages(it, LOWEST_PRIORITY))
- }
+ if (library.hashPages)
+ mediaRepository.findAllBookIdsByLibraryIdAndWithMissingPageHash(library.id, komgaProperties.pageHashing).forEach {
+ submitTask(Task.HashBookPages(it, LOWEST_PRIORITY))
+ }
}
fun convertBooksToCbz(library: Library, priority: Int = DEFAULT_PRIORITY) {
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt
index 06631d653..672502e09 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt
@@ -25,6 +25,9 @@ data class Library(
val convertToCbz: Boolean = false,
val emptyTrashAfterScan: Boolean = false,
val seriesCover: SeriesCover = SeriesCover.FIRST,
+ val hashFiles: Boolean = true,
+ val hashPages: Boolean = false,
+ val analyzeDimensions: Boolean = true,
val unavailableDate: LocalDateTime? = null,
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 ab2f65d8f..e94b11369 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
@@ -38,7 +38,7 @@ class BookAnalyzer(
private val thumbnailSize = 300
private val thumbnailFormat = "jpeg"
- fun analyze(book: Book): Media {
+ fun analyze(book: Book, analyzeDimensions: Boolean): Media {
logger.info { "Trying to analyze book: $book" }
try {
val mediaType = contentDetector.detectMediaType(book.path)
@@ -47,7 +47,7 @@ class BookAnalyzer(
return Media(mediaType = mediaType, status = Media.Status.UNSUPPORTED, comment = "ERR_1001", bookId = book.id)
val entries = try {
- supportedMediaTypes.getValue(mediaType).getEntries(book.path)
+ supportedMediaTypes.getValue(mediaType).getEntries(book.path, analyzeDimensions)
} catch (ex: MediaUnsupportedException) {
return Media(mediaType = mediaType, status = Media.Status.UNSUPPORTED, comment = ex.code, bookId = book.id)
} catch (ex: Exception) {
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt
index 8cadb1d97..0c9551acc 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt
@@ -100,7 +100,7 @@ class BookConverter(
)
?: throw IllegalStateException("Newly converted book could not be scanned: $destinationFilename")
- val convertedMedia = bookAnalyzer.analyze(convertedBook)
+ val convertedMedia = bookAnalyzer.analyze(convertedBook, libraryRepository.findById(book.libraryId).analyzeDimensions)
try {
when {
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 017a5ee56..0ebd02bfc 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
@@ -16,11 +16,11 @@ import org.gotson.komga.domain.model.ThumbnailBook
import org.gotson.komga.domain.model.withCode
import org.gotson.komga.domain.persistence.BookMetadataRepository
import org.gotson.komga.domain.persistence.BookRepository
+import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.persistence.MediaRepository
import org.gotson.komga.domain.persistence.ReadListRepository
import org.gotson.komga.domain.persistence.ReadProgressRepository
import org.gotson.komga.domain.persistence.ThumbnailBookRepository
-import org.gotson.komga.infrastructure.configuration.KomgaProperties
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.image.ImageConverter
import org.gotson.komga.infrastructure.image.ImageType
@@ -47,17 +47,17 @@ class BookLifecycle(
private val readProgressRepository: ReadProgressRepository,
private val thumbnailBookRepository: ThumbnailBookRepository,
private val readListRepository: ReadListRepository,
+ private val libraryRepository: LibraryRepository,
private val bookAnalyzer: BookAnalyzer,
private val imageConverter: ImageConverter,
private val eventPublisher: EventPublisher,
private val transactionTemplate: TransactionTemplate,
private val hasher: Hasher,
- private val komgaProperties: KomgaProperties,
) {
fun analyzeAndPersist(book: Book): Boolean {
logger.info { "Analyze and persist book: $book" }
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, libraryRepository.findById(book.libraryId).analyzeDimensions)
transactionTemplate.executeWithoutResult {
// if the number of pages has changed, delete all read progress for that book
@@ -77,8 +77,8 @@ class BookLifecycle(
}
fun hashAndPersist(book: Book) {
- if (!komgaProperties.fileHashing)
- return logger.info { "File hashing is disabled, it may have changed since the task was submitted, skipping" }
+ if (!libraryRepository.findById(book.libraryId).hashFiles)
+ return logger.info { "File hashing is disabled for the library, it may have changed since the task was submitted, skipping" }
logger.info { "Hash and persist book: $book" }
if (book.fileHash.isBlank()) {
@@ -90,6 +90,9 @@ class BookLifecycle(
}
fun hashPagesAndPersist(book: Book) {
+ if (!libraryRepository.findById(book.libraryId).hashPages)
+ return logger.info { "Page hashing is disabled for the library, it may have changed since the task was submitted, skipping" }
+
logger.info { "Hash and persist pages for book: $book" }
mediaRepository.update(bookAnalyzer.hashPages(BookWithMedia(book, mediaRepository.findById(book.id))))
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt
index cafbda65e..9e0743eb8 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt
@@ -33,7 +33,7 @@ class TransientBookLifecycle(
}
fun analyzeAndPersist(transientBook: BookWithMedia): BookWithMedia {
- val media = bookAnalyzer.analyze(transientBook.book)
+ val media = bookAnalyzer.analyze(transientBook.book, true)
val updated = transientBook.copy(media = media)
transientBookRepository.save(updated)
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt
index 7ecc8a08a..fd877bad6 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt
@@ -23,6 +23,7 @@ class KomgaProperties {
var deleteEmptyCollections: Boolean = true
+ @Deprecated("Deprecated since 0.143.0, you can configure this in the library options directly")
var fileHashing: Boolean = true
@Positive
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt
index fd2b7dcf1..fc87c59a0 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt
@@ -75,8 +75,11 @@ class LibraryDao(
.set(l.REPAIR_EXTENSIONS, library.repairExtensions)
.set(l.CONVERT_TO_CBZ, library.convertToCbz)
.set(l.EMPTY_TRASH_AFTER_SCAN, library.emptyTrashAfterScan)
- .set(l.UNAVAILABLE_DATE, library.unavailableDate)
.set(l.SERIES_COVER, library.seriesCover.toString())
+ .set(l.HASH_FILES, library.hashFiles)
+ .set(l.HASH_PAGES, library.hashPages)
+ .set(l.ANALYZE_DIMENSIONS, library.analyzeDimensions)
+ .set(l.UNAVAILABLE_DATE, library.unavailableDate)
.execute()
}
@@ -100,6 +103,9 @@ class LibraryDao(
.set(l.CONVERT_TO_CBZ, library.convertToCbz)
.set(l.EMPTY_TRASH_AFTER_SCAN, library.emptyTrashAfterScan)
.set(l.SERIES_COVER, library.seriesCover.toString())
+ .set(l.HASH_FILES, library.hashFiles)
+ .set(l.HASH_PAGES, library.hashPages)
+ .set(l.ANALYZE_DIMENSIONS, library.analyzeDimensions)
.set(l.UNAVAILABLE_DATE, library.unavailableDate)
.set(l.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
.where(l.ID.eq(library.id))
@@ -127,6 +133,10 @@ class LibraryDao(
convertToCbz = convertToCbz,
emptyTrashAfterScan = emptyTrashAfterScan,
seriesCover = Library.SeriesCover.valueOf(seriesCover),
+ hashFiles = hashFiles,
+ hashPages = hashPages,
+ analyzeDimensions = analyzeDimensions,
+
unavailableDate = unavailableDate,
id = id,
createdDate = createdDate.toCurrentTimeZone(),
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt
index 6827c7fef..264b8af7f 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt
@@ -22,7 +22,7 @@ class EpubExtractor(
override fun mediaTypes(): List = listOf("application/epub+zip")
- override fun getEntries(path: Path): List {
+ override fun getEntries(path: Path, analyzeDimensions: Boolean): List {
ZipFile(path.toFile()).use { zip ->
try {
val opfFile = getPackagePath(zip)
@@ -56,7 +56,7 @@ class EpubExtractor(
val mediaType = manifest.values.first {
it.href == (opfDir?.relativize(image) ?: image).invariantSeparatorsPathString
}.mediaType
- val dimension = if (contentDetector.isImage(mediaType))
+ val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType))
zip.getInputStream(zip.getEntry(name)).use { imageAnalyzer.getDimension(it) }
else
null
@@ -64,7 +64,7 @@ class EpubExtractor(
}
} catch (e: Exception) {
logger.error(e) { "File is not a proper Epub, treating it as a zip file" }
- return zipExtractor.getEntries(path)
+ return zipExtractor.getEntries(path, analyzeDimensions)
}
}
}
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/MediaContainerExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/MediaContainerExtractor.kt
index 9ba72dc6b..e71314af7 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/MediaContainerExtractor.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/MediaContainerExtractor.kt
@@ -8,7 +8,7 @@ interface MediaContainerExtractor {
fun mediaTypes(): List
@Throws(MediaUnsupportedException::class)
- fun getEntries(path: Path): List
+ fun getEntries(path: Path, analyzeDimensions: Boolean): List
fun getEntryStream(path: Path, entryName: String): ByteArray
}
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt
index 1f0240c16..c5ad65427 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt
@@ -35,12 +35,12 @@ class PdfExtractor(
override fun mediaTypes(): List = listOf("application/pdf")
- override fun getEntries(path: Path): List =
+ override fun getEntries(path: Path, analyzeDimensions: Boolean): List =
PDDocument.load(path.toFile()).use { pdf ->
(0 until pdf.numberOfPages).map { index ->
val page = pdf.getPage(index)
val scale = page.getScale()
- val dimension = Dimension((page.cropBox.width * scale).roundToInt(), (page.cropBox.height * scale).roundToInt())
+ val dimension = if (analyzeDimensions) Dimension((page.cropBox.width * scale).roundToInt(), (page.cropBox.height * scale).roundToInt()) else null
MediaContainerEntry(name = index.toString(), mediaType = mediaType, dimension = dimension)
}
}
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt
index 085484a16..d230ed458 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt
@@ -21,7 +21,7 @@ class RarExtractor(
override fun mediaTypes(): List = listOf("application/x-rar-compressed", "application/x-rar-compressed; version=4")
- override fun getEntries(path: Path): List =
+ override fun getEntries(path: Path, analyzeDimensions: Boolean): List =
Archive(path.toFile()).use { rar ->
if (rar.isPasswordProtected) throw MediaUnsupportedException("Encrypted RAR archives are not supported", "ERR_1002")
if (rar.mainHeader.isSolid) throw MediaUnsupportedException("Solid RAR archives are not supported", "ERR_1003")
@@ -32,7 +32,7 @@ class RarExtractor(
try {
val buffer = rar.getInputStream(entry).use { it.readBytes() }
val mediaType = buffer.inputStream().use { contentDetector.detectMediaType(it) }
- val dimension = if (contentDetector.isImage(mediaType))
+ val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType))
buffer.inputStream().use { imageAnalyzer.getDimension(it) }
else
null
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt
index b84b412c4..33161b042 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt
@@ -28,7 +28,7 @@ class ZipExtractor(
override fun mediaTypes(): List = listOf("application/zip")
- override fun getEntries(path: Path): List =
+ override fun getEntries(path: Path, analyzeDimensions: Boolean): List =
ZipFile(path.toFile()).use { zip ->
zip.entries.toList()
.filter { !it.isDirectory }
@@ -36,7 +36,7 @@ class ZipExtractor(
try {
zip.getInputStream(entry).buffered().use { stream ->
val mediaType = contentDetector.detectMediaType(stream)
- val dimension = if (contentDetector.isImage(mediaType))
+ val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType))
imageAnalyzer.getDimension(stream)
else
null
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt
index bf6d0a04b..941a5afd5 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt
@@ -92,6 +92,9 @@ class LibraryController(
convertToCbz = library.convertToCbz,
emptyTrashAfterScan = library.emptyTrashAfterScan,
seriesCover = library.seriesCover.toDomain(),
+ hashFiles = library.hashFiles,
+ hashPages = library.hashPages,
+ analyzeDimensions = library.analyzeDimensions,
),
).toDto(includeRoot = principal.user.roleAdmin)
} catch (e: Exception) {
@@ -133,6 +136,9 @@ class LibraryController(
convertToCbz = library.convertToCbz,
emptyTrashAfterScan = library.emptyTrashAfterScan,
seriesCover = library.seriesCover.toDomain(),
+ hashFiles = library.hashFiles,
+ hashPages = library.hashPages,
+ analyzeDimensions = library.analyzeDimensions,
)
try {
libraryLifecycle.updateLibrary(toUpdate)
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryCreationDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryCreationDto.kt
index d49e22cda..5a60ee10e 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryCreationDto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryCreationDto.kt
@@ -20,4 +20,7 @@ data class LibraryCreationDto(
val convertToCbz: Boolean = false,
val emptyTrashAfterScan: Boolean = false,
val seriesCover: SeriesCoverDto = SeriesCoverDto.FIRST,
+ val hashFiles: Boolean = true,
+ val hashPages: Boolean = false,
+ val analyzeDimensions: Boolean = true,
)
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt
index a19d0b6a8..09157df74 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt
@@ -22,6 +22,9 @@ data class LibraryDto(
val convertToCbz: Boolean,
val emptyTrashAfterScan: Boolean,
val seriesCover: SeriesCoverDto,
+ val hashFiles: Boolean,
+ val hashPages: Boolean,
+ val analyzeDimensions: Boolean,
val unavailable: Boolean,
)
@@ -44,5 +47,8 @@ fun Library.toDto(includeRoot: Boolean) = LibraryDto(
convertToCbz = convertToCbz,
emptyTrashAfterScan = emptyTrashAfterScan,
seriesCover = seriesCover.toDto(),
+ hashFiles = hashFiles,
+ hashPages = hashPages,
+ analyzeDimensions = analyzeDimensions,
unavailable = unavailableDate != null,
)
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt
index 5aabaee3d..f45cde464 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt
@@ -20,4 +20,7 @@ data class LibraryUpdateDto(
val convertToCbz: Boolean,
val emptyTrashAfterScan: Boolean,
val seriesCover: SeriesCoverDto,
+ val hashFiles: Boolean,
+ val hashPages: Boolean,
+ val analyzeDimensions: Boolean,
)
diff --git a/komga/src/main/resources/application-dev.yml b/komga/src/main/resources/application-dev.yml
index 7babe20da..f47c3e260 100644
--- a/komga/src/main/resources/application-dev.yml
+++ b/komga/src/main/resources/application-dev.yml
@@ -9,7 +9,6 @@ komga:
file: ":memory:"
cors.allowed-origins:
- http://localhost:8081
-# file-hashing: false
# delete-empty-collections: true
# delete-empty-read-lists: true
oauth2-account-creation: false
diff --git a/komga/src/main/resources/application.yml b/komga/src/main/resources/application.yml
index 4972cbd57..b60a59413 100644
--- a/komga/src/main/resources/application.yml
+++ b/komga/src/main/resources/application.yml
@@ -28,6 +28,8 @@ spring:
enabled: true
locations: classpath:db/migration/{vendor}
mixed: true
+ placeholders:
+ library-file-hashing: \${komga.file-hashing:true}
thymeleaf:
prefix: classpath:/public/
mvc:
diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt
index 007ecb8aa..914b46cc9 100644
--- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt
@@ -2,6 +2,7 @@ package org.gotson.komga.domain.service
import com.ninjasquad.springmockk.SpykBean
import io.mockk.every
+import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookPage
@@ -40,7 +41,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/rar4.rar")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/x-rar-compressed; version=4")
assertThat(media.status).isEqualTo(Media.Status.READY)
@@ -57,7 +58,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/rar4-solid.rar")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/x-rar-compressed; version=4")
assertThat(media.status).isEqualTo(Media.Status.UNSUPPORTED)
@@ -73,7 +74,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/$fileName")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/x-rar-compressed; version=5")
assertThat(media.status).isEqualTo(Media.Status.UNSUPPORTED)
@@ -89,7 +90,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/$fileName")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/x-7z-compressed")
assertThat(media.status).isEqualTo(Media.Status.UNSUPPORTED)
@@ -105,7 +106,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/$fileName")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/zip")
assertThat(media.status).isEqualTo(Media.Status.READY)
@@ -117,7 +118,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/zip-encrypted.zip")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/zip")
assertThat(media.status).isEqualTo(Media.Status.ERROR)
@@ -128,7 +129,7 @@ class BookAnalyzerTest(
val file = ClassPathResource("archives/epub3.epub")
val book = Book("book", file.url, LocalDateTime.now())
- val media = bookAnalyzer.analyze(book)
+ val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/epub+zip")
assertThat(media.status).isEqualTo(Media.Status.READY)
diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt
index dc875b76f..b59bdf545 100644
--- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt
@@ -100,7 +100,7 @@ class BookLifecycleTest(
assertThat(readProgressRepository.findAll()).hasSize(2)
// when
- every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book.id)
+ every { mockAnalyzer.analyze(any(), any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book.id)
bookLifecycle.analyzeAndPersist(book)
// then
@@ -133,7 +133,7 @@ class BookLifecycleTest(
assertThat(readProgressRepository.findAll()).hasSize(2)
// when
- every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = (1..10).map { BookPage("$it", "image/jpeg") }, bookId = book.id)
+ every { mockAnalyzer.analyze(any(), any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = (1..10).map { BookPage("$it", "image/jpeg") }, bookId = book.id)
bookLifecycle.analyzeAndPersist(book)
// then
diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt
index acc84a890..9cd37799a 100644
--- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt
@@ -284,7 +284,7 @@ class LibraryContentLifecycleTest(
)
libraryContentLifecycle.scanRootFolder(library)
- every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
+ every { mockAnalyzer.analyze(any(), any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
bookRepository.findAll().map { bookLifecycle.analyzeAndPersist(it) }
// when
@@ -292,7 +292,7 @@ class LibraryContentLifecycleTest(
// then
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
- verify(exactly = 1) { mockAnalyzer.analyze(any()) }
+ verify(exactly = 1) { mockAnalyzer.analyze(any(), any()) }
bookRepository.findAll().first().let { book ->
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
@@ -320,7 +320,7 @@ class LibraryContentLifecycleTest(
)
libraryContentLifecycle.scanRootFolder(library)
- every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
+ every { mockAnalyzer.analyze(any(), any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
every { mockHasher.computeHash(any()) }.returnsMany("abc", "def")
bookRepository.findAll().map {
@@ -333,7 +333,7 @@ class LibraryContentLifecycleTest(
// then
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
- verify(exactly = 1) { mockAnalyzer.analyze(any()) }
+ verify(exactly = 1) { mockAnalyzer.analyze(any(), any()) }
verify(exactly = 1) { mockHasher.computeHash(any()) }
bookRepository.findAll().first().let { book ->
diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt
index dc8b91377..cff44dc7a 100644
--- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt
@@ -68,6 +68,9 @@ class LibraryDaoTest(
convertToCbz = true,
emptyTrashAfterScan = true,
seriesCover = Library.SeriesCover.LAST,
+ hashFiles = false,
+ hashPages = true,
+ analyzeDimensions = false,
)
}
@@ -95,6 +98,9 @@ class LibraryDaoTest(
assertThat(modified.convertToCbz).isEqualTo(updated.convertToCbz)
assertThat(modified.emptyTrashAfterScan).isEqualTo(updated.emptyTrashAfterScan)
assertThat(modified.seriesCover).isEqualTo(updated.seriesCover)
+ assertThat(modified.hashFiles).isEqualTo(updated.hashFiles)
+ assertThat(modified.hashPages).isEqualTo(updated.hashPages)
+ assertThat(modified.analyzeDimensions).isEqualTo(updated.analyzeDimensions)
}
@Test
diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt
index 8b5a4714a..c05609362 100644
--- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt
@@ -19,7 +19,7 @@ class EpubExtractorTest {
fun `given epub 3 file when parsing for entries then returns all images contained in pages`() {
val epubResource = ClassPathResource("epub/The Incomplete Theft - Ralph Burke.epub")
- val entries = epubExtractor.getEntries(epubResource.file.toPath())
+ val entries = epubExtractor.getEntries(epubResource.file.toPath(), true)
assertThat(entries).hasSize(1)
with(entries.first()) {
@@ -28,4 +28,18 @@ class EpubExtractorTest {
assertThat(dimension).isEqualTo(Dimension(461, 616))
}
}
+
+ @Test
+ fun `given epub 3 file when parsing for entries without analyzing dimensions then returns all images contained in pages without dimensions`() {
+ val epubResource = ClassPathResource("epub/The Incomplete Theft - Ralph Burke.epub")
+
+ val entries = epubExtractor.getEntries(epubResource.file.toPath(), false)
+
+ assertThat(entries).hasSize(1)
+ with(entries.first()) {
+ assertThat(name).isEqualTo("cover.jpeg")
+ assertThat(mediaType).isEqualTo("image/jpeg")
+ assertThat(dimension).isNull()
+ }
+ }
}