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:
Gauthier Roebroeck 2019-11-22 17:43:00 +08:00
parent c55af09c8a
commit 04f576c810
10 changed files with 28 additions and 113 deletions

View file

@ -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())

View file

@ -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 {

View file

@ -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>

View file

@ -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()
}

View file

@ -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(),

View file

@ -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() }
}
}

View file

@ -0,0 +1,6 @@
alter table book
add (number float4 default 0);
update book
set number = (index + 1);
alter table book
drop column index;

View file

@ -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 {

View file

@ -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")
}
}

View file

@ -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)
}