mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
fix: prevent transient scanning of directories that are part of existing libraries
This commit is contained in:
parent
e83eded07b
commit
8a92b84fd0
6 changed files with 46 additions and 13 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue