Skip to content

Commit

Permalink
feat: allow book import for one-shots
Browse files Browse the repository at this point in the history
  • Loading branch information
gotson committed Jan 14, 2025
1 parent 41cdf80 commit 51416c5
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 18 deletions.
18 changes: 13 additions & 5 deletions komga-webui/src/components/FileImportRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@
<template v-else>
<div style="height: 2em" class="missing"></div>
</template>
<series-picker-dialog v-model="modalSeriesPicker" @update:series="pickedSeries" :include-oneshots="false"/>
<series-picker-dialog v-model="modalSeriesPicker" @update:series="pickedSeries" :include-oneshots="true"/>
</td>

<!-- Book number chooser -->
<td>
<v-text-field v-model.number="bookNumber"
<v-text-field v-if="!selectedSeries?.oneshot"
v-model.number="bookNumber"
type="number"
step="0.1"
dense
:disabled="!selectedSeries"
/>
<span v-else>{{ $t('common.oneshot') }}</span>
</td>

<!-- Book details -->
Expand Down Expand Up @@ -129,7 +131,7 @@ import TransientBookViewerDialog from '@/components/dialogs/TransientBookViewerD
import {bookPageUrl, transientBookPageUrl} from '@/functions/urls'
import {convertErrorCodes} from '@/functions/error-codes'
import FileNameChooserDialog from '@/components/dialogs/FileNameChooserDialog.vue'
import {ReadListRequestBookMatchSeriesDto} from '@/types/komga-readlists'
import {SeriesSelected} from '@/types/series-slim'
export default Vue.extend({
name: 'FileImportRow',
Expand Down Expand Up @@ -167,6 +169,7 @@ export default Vue.extend({
if (val) this.selectedSeries = {
seriesId: val.id,
title: val.metadata.title,
oneshot: val.oneshot,
}
},
immediate: true,
Expand All @@ -189,7 +192,7 @@ export default Vue.extend({
convertErrorCodes,
innerSelect: false,
bookAnalyzed: undefined as unknown as TransientBookDto,
selectedSeries: undefined as ReadListRequestBookMatchSeriesDto | undefined,
selectedSeries: undefined as SeriesSelected | undefined,
seriesBooks: [] as BookDto[],
bookToUpgrade: undefined as BookDto | undefined,
bookToUpgradePages: [] as PageDto[],
Expand Down Expand Up @@ -253,12 +256,16 @@ export default Vue.extend({
this.selectedSeries = {
seriesId: seriesDto.id,
title: seriesDto.metadata.title,
oneshot: seriesDto.oneshot,
}
}
},
async getSeriesBooks(series: ReadListRequestBookMatchSeriesDto) {
async getSeriesBooks(series: SeriesSelected) {
if (series) {
this.seriesBooks = (await this.$komgaSeries.getBooks(series.seriesId, {unpaged: true})).content
if (series.oneshot) {
this.bookNumber = this.seriesBooks[0].metadata.numberSort
}
this.checkForUpgrade(this.bookNumber)
}
},
Expand All @@ -271,6 +278,7 @@ export default Vue.extend({
this.selectedSeries = {
seriesId: series.id,
title: series.metadata.title,
oneshot: series.oneshot,
}
},
},
Expand Down
5 changes: 5 additions & 0 deletions komga-webui/src/types/series-slim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface SeriesSelected {
seriesId: string,
title: string,
oneshot: boolean,
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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.SeriesRepository
import org.gotson.komga.domain.persistence.SidecarRepository
import org.gotson.komga.domain.persistence.ThumbnailBookRepository
import org.gotson.komga.language.toIndexedMap
Expand Down Expand Up @@ -62,6 +63,7 @@ class BookImporter(
private val eventPublisher: ApplicationEventPublisher,
private val taskEmitter: TaskEmitter,
private val historicalEventRepository: HistoricalEventRepository,
private val seriesRepository: SeriesRepository,
) {
fun importBook(
sourceFile: Path,
Expand All @@ -72,22 +74,37 @@ class BookImporter(
): Book {
try {
if (sourceFile.notExists()) throw FileNotFoundException("File not found: $sourceFile").withCode("ERR_1018")
if (series.oneshot) throw IllegalArgumentException("Destination series is oneshot")
if (series.oneshot && upgradeBookId.isNullOrEmpty()) throw IllegalArgumentException("Destination series is oneshot but upgradeBookId is missing")

libraryRepository.findAll().forEach { library ->
if (sourceFile.startsWith(library.path)) throw PathContainedInPath("Cannot import file that is part of an existing library", "ERR_1019")
}

val bookToUpgrade =
if (upgradeBookId != null) {
bookRepository.findByIdOrNull(upgradeBookId)?.also {
if (it.seriesId != series.id) throw IllegalArgumentException("Book to upgrade ($upgradeBookId) does not belong to series: $series").withCode("ERR_1020")
}
} else {
null
}

val destDir =
if (series.oneshot)
series.path.parent
else
series.path

val destFile =
series.path.resolve(
destDir.resolve(
if (destinationName != null)
Paths.get("$destinationName.${sourceFile.extension}").name
else
sourceFile.name,
)
val sidecars =
fileSystemScanner.scanBookSidecars(sourceFile).associateWith {
series.path.resolve(
destDir.resolve(
if (destinationName != null)
it.url
.toURI()
Expand All @@ -102,15 +119,6 @@ class BookImporter(
)
}

val bookToUpgrade =
if (upgradeBookId != null) {
bookRepository.findByIdOrNull(upgradeBookId)?.also {
if (it.seriesId != series.id) throw IllegalArgumentException("Book to upgrade ($upgradeBookId) does not belong to series: $series").withCode("ERR_1020")
}
} else {
null
}

var deletedUpgradedFile = false
when {
bookToUpgrade?.path != null && destFile == bookToUpgrade.path -> {
Expand Down Expand Up @@ -184,7 +192,7 @@ class BookImporter(
val importedBook =
fileSystemScanner
.scanFile(destFile)
?.copy(libraryId = series.libraryId)
?.copy(libraryId = series.libraryId, oneshot = series.oneshot)
?: throw IllegalStateException("Newly imported book could not be scanned: $destFile").withCode("ERR_1022")

seriesLifecycle.addBooks(series, listOf(importedBook))
Expand Down Expand Up @@ -238,6 +246,11 @@ class BookImporter(

// delete upgraded book
bookLifecycle.deleteOne(bookToUpgrade)

// update series if one-shot, so it's not marked as not found during the next scan
if (series.oneshot) {
seriesRepository.update(series.copy(url = importedBook.url, fileLastModified = importedBook.fileLastModified))
}
}

seriesLifecycle.sortBooks(series)
Expand Down

0 comments on commit 51416c5

Please sign in to comment.