mirror of
https://github.com/gotson/komga.git
synced 2026-04-19 13:31:15 +02:00
remove @OrderedColumn on Series to Books association
replace with number on Book.kt to represent chapter/issue number remove series.name and book.name updates in LibraryScanner.kt, the name is computed from the url so this case cannot happen
This commit is contained in:
parent
c55af09c8a
commit
04f576c810
10 changed files with 28 additions and 113 deletions
|
|
@ -53,6 +53,9 @@ class Book(
|
|||
field = value
|
||||
}
|
||||
|
||||
@Column(name = "number", nullable = false, columnDefinition = "REAL")
|
||||
var number: Float = 0F
|
||||
|
||||
fun fileName(): String = FilenameUtils.getName(url.toString())
|
||||
|
||||
fun fileExtension(): String = FilenameUtils.getExtension(url.toString())
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import javax.persistence.Id
|
|||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.OneToMany
|
||||
import javax.persistence.OrderColumn
|
||||
import javax.persistence.Table
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
|
|
@ -47,7 +46,6 @@ class Series(
|
|||
lateinit var library: Library
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "series")
|
||||
@OrderColumn(name = "index")
|
||||
private var _books: MutableList<Book> = mutableListOf()
|
||||
|
||||
var books: List<Book>
|
||||
|
|
@ -56,6 +54,7 @@ class Series(
|
|||
_books.clear()
|
||||
value.forEach { it.series = this }
|
||||
_books.addAll(value.sortedWith(compareBy(natSortComparator) { it.name }))
|
||||
_books.forEachIndexed { index, book -> book.number = index + 1F }
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
|
|||
|
|
@ -7,24 +7,13 @@ import org.springframework.data.domain.Page
|
|||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URL
|
||||
|
||||
@Repository
|
||||
interface BookRepository : JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {
|
||||
@Query(
|
||||
value = "select * from Book b where b.series_id = ?1 order by b.index",
|
||||
countQuery = "select count(*) from Book where series_id = ?1",
|
||||
nativeQuery = true)
|
||||
fun findAllBySeriesId(seriesId: Long, pageable: Pageable): Page<Book>
|
||||
|
||||
@Query(
|
||||
value = "select * from Book b, Book_Metadata m where b.book_metadata_id = m.id " +
|
||||
"and b.series_id = ?2 and m.status = ?1 order by b.index",
|
||||
countQuery = "select count(*) from Book b, Book_Metadata m where b.book_metadata_id = m.id and b.series_id = ?2 and m.status = ?1",
|
||||
nativeQuery = true)
|
||||
fun findAllByMetadataStatusAndSeriesId(status: String, seriesId: Long, pageable: Pageable): Page<Book>
|
||||
fun findAllByMetadataStatusAndSeriesId(status: BookMetadata.Status, seriesId: Long, pageable: Pageable): Page<Book>
|
||||
|
||||
fun findByUrl(url: URL): Book?
|
||||
fun findAllByMetadataStatus(status: BookMetadata.Status): List<Book>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ class LibraryScanner(
|
|||
// if series already exists, update it
|
||||
if (newSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) {
|
||||
logger.info { "Series changed on disk, updating: $newSeries" }
|
||||
existingSeries.name = newSeries.name
|
||||
existingSeries.fileLastModified = newSeries.fileLastModified
|
||||
|
||||
// update list of books with existing entities if they exist
|
||||
|
|
@ -62,7 +61,6 @@ class LibraryScanner(
|
|||
if (newBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) {
|
||||
logger.info { "Book changed on disk, update and reset metadata status: $newBook" }
|
||||
existingBook.fileLastModified = newBook.fileLastModified
|
||||
existingBook.name = newBook.name
|
||||
existingBook.fileSize = newBook.fileSize
|
||||
existingBook.metadata.reset()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ data class BookDto(
|
|||
val id: Long,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val number: Float,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val lastModified: LocalDateTime?,
|
||||
val sizeBytes: Long,
|
||||
|
|
@ -46,6 +47,7 @@ fun Book.toDto() =
|
|||
id = id,
|
||||
name = name,
|
||||
url = url.toURI().path,
|
||||
number = number,
|
||||
lastModified = lastModifiedDate?.toUTC(),
|
||||
sizeBytes = fileSize,
|
||||
size = fileSizeHumanReadable(),
|
||||
|
|
|
|||
|
|
@ -131,10 +131,17 @@ class SeriesController(
|
|||
if (!principal.user.canAccessSeries(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
val pageRequest = PageRequest.of(
|
||||
page.pageNumber,
|
||||
page.pageSize,
|
||||
if (page.sort.isSorted) page.sort
|
||||
else Sort.by(Sort.Order.asc("number"))
|
||||
)
|
||||
|
||||
return if (readyFilter) {
|
||||
bookRepository.findAllByMetadataStatusAndSeriesId(BookMetadata.Status.READY.name, id, page)
|
||||
bookRepository.findAllByMetadataStatusAndSeriesId(BookMetadata.Status.READY, id, pageRequest)
|
||||
} else {
|
||||
bookRepository.findAllBySeriesId(id, page)
|
||||
bookRepository.findAllBySeriesId(id, pageRequest)
|
||||
}.map { it.toDto() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
alter table book
|
||||
add (number float4 default 0);
|
||||
update book
|
||||
set number = (index + 1);
|
||||
alter table book
|
||||
drop column index;
|
||||
|
|
@ -3,14 +3,14 @@ package org.gotson.komga.domain.model
|
|||
import java.net.URL
|
||||
import java.time.LocalDateTime
|
||||
|
||||
fun makeBook(name: String, url: String = "file:/$name", fileLastModified: LocalDateTime = LocalDateTime.now()): Book {
|
||||
fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now()): Book {
|
||||
Thread.sleep(5)
|
||||
return Book(name = name, url = URL(url), fileLastModified = fileLastModified)
|
||||
return Book(name = name, url = URL("file:/$name"), fileLastModified = fileLastModified)
|
||||
}
|
||||
|
||||
fun makeSeries(name: String, url: String = "file:/$name", books: List<Book> = listOf()): Series {
|
||||
fun makeSeries(name: String, books: List<Book> = listOf()): Series {
|
||||
Thread.sleep(5)
|
||||
return Series(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
|
||||
return Series(name = name, url = URL("file:/$name"), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
|
||||
}
|
||||
|
||||
fun makeLibrary(name: String = "default", url: String = "file:/$name"): Library {
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
package org.gotson.komga.domain.persistence
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.makeBook
|
||||
import org.gotson.komga.domain.model.makeLibrary
|
||||
import org.gotson.komga.domain.model.makeSeries
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@DataJpaTest
|
||||
@Transactional
|
||||
class BookRepositoryTest(
|
||||
@Autowired private val seriesRepository: SeriesRepository,
|
||||
@Autowired private val bookRepository: BookRepository,
|
||||
@Autowired private val libraryRepository: LibraryRepository
|
||||
) {
|
||||
|
||||
private val library = makeLibrary()
|
||||
|
||||
@BeforeAll
|
||||
fun `setup library`() {
|
||||
libraryRepository.save(library)
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun `teardown library`() {
|
||||
libraryRepository.deleteAll()
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun `clear repository`() {
|
||||
seriesRepository.deleteAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given many books with unordered index when fetching then books are ordered and paged`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
|
||||
seriesRepository.save(series)
|
||||
|
||||
val pageable = PageRequest.of(0, 20)
|
||||
val pageOfBooks = bookRepository.findAllBySeriesId(series.id, pageable)
|
||||
|
||||
assertThat(pageOfBooks.isFirst).isTrue()
|
||||
assertThat(pageOfBooks.isLast).isFalse()
|
||||
assertThat(pageOfBooks.size).isEqualTo(20)
|
||||
assertThat(pageOfBooks.number).isEqualTo(0)
|
||||
assertThat(pageOfBooks.content).hasSize(20)
|
||||
assertThat(pageOfBooks.content.map { it.name }).startsWith("1", "2", "3", "5")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given many books in ready state with unordered index when fetching then books are ordered and paged`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
|
||||
series.books.forEach { it.metadata = BookMetadata(BookMetadata.Status.READY) }
|
||||
seriesRepository.save(series)
|
||||
|
||||
val pageable = PageRequest.of(0, 20)
|
||||
val pageOfBooks = bookRepository.findAllBySeriesId(series.id, pageable)
|
||||
|
||||
assertThat(pageOfBooks.isFirst).isTrue()
|
||||
assertThat(pageOfBooks.isLast).isFalse()
|
||||
assertThat(pageOfBooks.size).isEqualTo(20)
|
||||
assertThat(pageOfBooks.number).isEqualTo(0)
|
||||
assertThat(pageOfBooks.content).hasSize(20)
|
||||
assertThat(pageOfBooks.content.map { it.name }).startsWith("1", "2", "3", "5")
|
||||
}
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ class LibraryScannerTest(
|
|||
val library = libraryRepository.save(makeLibrary())
|
||||
|
||||
val series = makeSeries(name = "series", books = listOf(makeBook("book1")))
|
||||
val seriesWithUpdatedBooks = makeSeries(name = "series", books = listOf(makeBook("book1updated", "file:/book1")))
|
||||
val seriesWithUpdatedBooks = makeSeries(name = "series", books = listOf(makeBook("book1")))
|
||||
|
||||
every { mockScanner.scanRootFolder(any()) }
|
||||
.returnsMany(
|
||||
|
|
@ -130,7 +130,7 @@ class LibraryScannerTest(
|
|||
assertThat(allSeries).hasSize(1)
|
||||
assertThat(allSeries.first().lastModifiedDate).isNotEqualTo(allSeries.first().createdDate)
|
||||
assertThat(allSeries.first().books).hasSize(1)
|
||||
assertThat(allSeries.first().books.map { it.name }).containsExactly("book1updated")
|
||||
assertThat(allSeries.first().books.map { it.name }).containsExactly("book1")
|
||||
assertThat(allSeries.first().books.first().lastModifiedDate).isNotEqualTo(allSeries.first().books.first().createdDate)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue