diff --git a/ERRORCODES.md b/ERRORCODES.md index f1eb98ca5..dd2a9d9a0 100644 --- a/ERRORCODES.md +++ b/ERRORCODES.md @@ -20,3 +20,4 @@ ERR_1013 | No unique match for book number within series ERR_1014 | No match for book number within series ERR_1015 | Error while deserializing ComicRack ReadingList ERR_1016 | Directory not accessible or not a directory +ERR_1017 | Cannot scan folder that is part of an existing library diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json index 770c27745..d080a32d9 100644 --- a/komga-webui/src/locales/en.json +++ b/komga-webui/src/locales/en.json @@ -434,7 +434,9 @@ "ERR_1012": "No match for series", "ERR_1013": "No unique match for book number within series", "ERR_1014": "No match for book number within series", - "ERR_1015": "Error while deserializing ComicRack ReadingList" + "ERR_1015": "Error while deserializing ComicRack ReadingList", + "ERR_1016": "Directory not accessible or not a directory", + "ERR_1017": "Cannot scan folder that is part of an existing library" }, "filter": { "age_rating": "age rating", diff --git a/komga-webui/src/services/komga-transientbooks.service.ts b/komga-webui/src/services/komga-transientbooks.service.ts index e9709048f..7bf7e7b32 100644 --- a/komga-webui/src/services/komga-transientbooks.service.ts +++ b/komga-webui/src/services/komga-transientbooks.service.ts @@ -6,25 +6,22 @@ const API_TRANSIENT_BOOKS = '/api/v1/transient-books' export default class KomgaTransientBooksService { private http: AxiosInstance - constructor (http: AxiosInstance) { + constructor(http: AxiosInstance) { this.http = http } - async scanForTransientBooks (path: string): Promise { + async scanForTransientBooks(path: string): Promise { try { return (await this.http.post(API_TRANSIENT_BOOKS, { path: path, })).data } catch (e) { - let msg = `An error occurred while trying to scan for transient book` - if (e.response.data.message) { - msg += `: ${e.response.data.message}` - } - throw new Error(msg) + if (e.response.data.message) throw new Error(e.response.data.message) + throw new Error('An error occurred while trying to scan for transient book') } } - async analyze (id: string): Promise { + async analyze(id: string): Promise { try { return (await this.http.post(`${API_TRANSIENT_BOOKS}/${id}/analyze`)).data } catch (e) { diff --git a/komga-webui/src/views/BookImport.vue b/komga-webui/src/views/BookImport.vue index 22073f885..c163d1f59 100644 --- a/komga-webui/src/views/BookImport.vue +++ b/komga-webui/src/views/BookImport.vue @@ -84,6 +84,19 @@ + + {{ snackText }} + {{ $t('common.close') }} + + + @@ -96,6 +109,7 @@ import SeriesPickerDialog from "@/components/dialogs/SeriesPickerDialog.vue"; import {SeriesDto} from "@/types/komga-series"; import {BookImportBatchDto, BookImportDto} from "@/types/komga-books"; import {CopyMode} from "@/types/enum-books"; +import {convertErrorCodes} from "@/functions/error-codes"; export default Vue.extend({ name: 'BookImport', @@ -110,6 +124,8 @@ export default Vue.extend({ transientBooks: [] as TransientBookDto[], copyMode: CopyMode.HARDLINK, importFinished: false, + snackbar: false, + snackText: '', }), computed: { globalSelect: { @@ -146,7 +162,11 @@ export default Vue.extend({ methods: { async scanBooks() { this.transientBooks = [] - this.transientBooks = await this.$komgaTransientBooks.scanForTransientBooks(this.importPath) + try { + this.transientBooks = await this.$komgaTransientBooks.scanForTransientBooks(this.importPath) + } catch (e) { + this.showSnack(convertErrorCodes(e.message)) + } this.selected = this.$_.range(this.transientBooks.length) this.payloads = this.payloads.splice(this.transientBooks.length, this.payloads.length) this.importFinished = false @@ -157,6 +177,10 @@ export default Vue.extend({ this.importFinished = true } }, + showSnack(message: string) { + this.snackText = message + this.snackbar = true + }, }, }) 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 349a7fe7a..4bc466810 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 @@ -4,6 +4,8 @@ import org.gotson.komga.domain.model.BookPageContent import org.gotson.komga.domain.model.BookWithMedia import org.gotson.komga.domain.model.Media import org.gotson.komga.domain.model.MediaNotReadyException +import org.gotson.komga.domain.model.PathContainedInPath +import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.persistence.TransientBookRepository import org.springframework.stereotype.Service import java.nio.file.Paths @@ -13,10 +15,17 @@ class TransientBookLifecycle( private val transientBookRepository: TransientBookRepository, private val bookAnalyzer: BookAnalyzer, private val fileSystemScanner: FileSystemScanner, + private val libraryRepository: LibraryRepository, ) { fun scanAndPersist(filePath: String): List { - val books = fileSystemScanner.scanRootFolder(Paths.get(filePath)).values.flatten().map { BookWithMedia(it, Media()) } + val folderToScan = Paths.get(filePath) + + libraryRepository.findAll().forEach { library -> + if (folderToScan.startsWith(library.path())) throw PathContainedInPath("Cannot scan folder that is part of an existing library", "ERR_1017") + } + + val books = fileSystemScanner.scanRootFolder(folderToScan).values.flatten().map { BookWithMedia(it, Media()) } transientBookRepository.saveAll(books) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/TransientBooksController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/TransientBooksController.kt index 312593eb7..f88ab239b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/TransientBooksController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/TransientBooksController.kt @@ -3,7 +3,7 @@ package org.gotson.komga.interfaces.rest import com.jakewharton.byteunits.BinaryByteUnit import mu.KotlinLogging import org.gotson.komga.domain.model.BookWithMedia -import org.gotson.komga.domain.model.DirectoryNotFoundException +import org.gotson.komga.domain.model.CodedException import org.gotson.komga.domain.model.MediaNotReadyException import org.gotson.komga.domain.model.ROLE_ADMIN import org.gotson.komga.domain.persistence.TransientBookRepository @@ -43,7 +43,7 @@ class TransientBooksController( transientBookLifecycle.scanAndPersist(request.path) .sortedBy { it.book.path() } .map { it.toDto() } - } catch (e: DirectoryNotFoundException) { + } catch (e: CodedException) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.code) }