fix: prevent transient scanning of directories that are part of existing libraries

This commit is contained in:
Gauthier Roebroeck 2021-04-20 16:00:36 +08:00
parent e83eded07b
commit 8a92b84fd0
6 changed files with 46 additions and 13 deletions

View file

@ -20,3 +20,4 @@ ERR_1013 | No unique match for book number within series
ERR_1014 | No 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_1016 | Directory not accessible or not a directory
ERR_1017 | Cannot scan folder that is part of an existing library

View file

@ -434,7 +434,9 @@
"ERR_1012": "No match for series", "ERR_1012": "No match for series",
"ERR_1013": "No unique match for book number within series", "ERR_1013": "No unique match for book number within series",
"ERR_1014": "No 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": { "filter": {
"age_rating": "age rating", "age_rating": "age rating",

View file

@ -6,25 +6,22 @@ const API_TRANSIENT_BOOKS = '/api/v1/transient-books'
export default class KomgaTransientBooksService { export default class KomgaTransientBooksService {
private http: AxiosInstance private http: AxiosInstance
constructor (http: AxiosInstance) { constructor(http: AxiosInstance) {
this.http = http this.http = http
} }
async scanForTransientBooks (path: string): Promise<TransientBookDto[]> { async scanForTransientBooks(path: string): Promise<TransientBookDto[]> {
try { try {
return (await this.http.post(API_TRANSIENT_BOOKS, { return (await this.http.post(API_TRANSIENT_BOOKS, {
path: path, path: path,
})).data })).data
} catch (e) { } catch (e) {
let msg = `An error occurred while trying to scan for transient book` if (e.response.data.message) throw new Error(e.response.data.message)
if (e.response.data.message) { throw new Error('An error occurred while trying to scan for transient book')
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
} }
} }
async analyze (id: string): Promise<TransientBookDto> { async analyze(id: string): Promise<TransientBookDto> {
try { try {
return (await this.http.post(`${API_TRANSIENT_BOOKS}/${id}/analyze`)).data return (await this.http.post(`${API_TRANSIENT_BOOKS}/${id}/analyze`)).data
} catch (e) { } catch (e) {

View file

@ -84,6 +84,19 @@
</v-row> </v-row>
</template> </template>
<v-snackbar
v-model="snackbar"
bottom
color="error"
>
{{ snackText }}
<v-btn
text
@click="snackbar = false"
>{{ $t('common.close') }}
</v-btn>
</v-snackbar>
</v-container> </v-container>
</template> </template>
@ -96,6 +109,7 @@ import SeriesPickerDialog from "@/components/dialogs/SeriesPickerDialog.vue";
import {SeriesDto} from "@/types/komga-series"; import {SeriesDto} from "@/types/komga-series";
import {BookImportBatchDto, BookImportDto} from "@/types/komga-books"; import {BookImportBatchDto, BookImportDto} from "@/types/komga-books";
import {CopyMode} from "@/types/enum-books"; import {CopyMode} from "@/types/enum-books";
import {convertErrorCodes} from "@/functions/error-codes";
export default Vue.extend({ export default Vue.extend({
name: 'BookImport', name: 'BookImport',
@ -110,6 +124,8 @@ export default Vue.extend({
transientBooks: [] as TransientBookDto[], transientBooks: [] as TransientBookDto[],
copyMode: CopyMode.HARDLINK, copyMode: CopyMode.HARDLINK,
importFinished: false, importFinished: false,
snackbar: false,
snackText: '',
}), }),
computed: { computed: {
globalSelect: { globalSelect: {
@ -146,7 +162,11 @@ export default Vue.extend({
methods: { methods: {
async scanBooks() { async scanBooks() {
this.transientBooks = [] 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.selected = this.$_.range(this.transientBooks.length)
this.payloads = this.payloads.splice(this.transientBooks.length, this.payloads.length) this.payloads = this.payloads.splice(this.transientBooks.length, this.payloads.length)
this.importFinished = false this.importFinished = false
@ -157,6 +177,10 @@ export default Vue.extend({
this.importFinished = true this.importFinished = true
} }
}, },
showSnack(message: string) {
this.snackText = message
this.snackbar = true
},
}, },
}) })
</script> </script>

View file

@ -4,6 +4,8 @@ import org.gotson.komga.domain.model.BookPageContent
import org.gotson.komga.domain.model.BookWithMedia import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.Media import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException 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.gotson.komga.domain.persistence.TransientBookRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.nio.file.Paths import java.nio.file.Paths
@ -13,10 +15,17 @@ class TransientBookLifecycle(
private val transientBookRepository: TransientBookRepository, private val transientBookRepository: TransientBookRepository,
private val bookAnalyzer: BookAnalyzer, private val bookAnalyzer: BookAnalyzer,
private val fileSystemScanner: FileSystemScanner, private val fileSystemScanner: FileSystemScanner,
private val libraryRepository: LibraryRepository,
) { ) {
fun scanAndPersist(filePath: String): List<BookWithMedia> { fun scanAndPersist(filePath: String): List<BookWithMedia> {
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) transientBookRepository.saveAll(books)

View file

@ -3,7 +3,7 @@ package org.gotson.komga.interfaces.rest
import com.jakewharton.byteunits.BinaryByteUnit import com.jakewharton.byteunits.BinaryByteUnit
import mu.KotlinLogging import mu.KotlinLogging
import org.gotson.komga.domain.model.BookWithMedia 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.MediaNotReadyException
import org.gotson.komga.domain.model.ROLE_ADMIN import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.persistence.TransientBookRepository import org.gotson.komga.domain.persistence.TransientBookRepository
@ -43,7 +43,7 @@ class TransientBooksController(
transientBookLifecycle.scanAndPersist(request.path) transientBookLifecycle.scanAndPersist(request.path)
.sortedBy { it.book.path() } .sortedBy { it.book.path() }
.map { it.toDto() } .map { it.toDto() }
} catch (e: DirectoryNotFoundException) { } catch (e: CodedException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.code) throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.code)
} }