mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
feat(api): add endpoint to empty trash per library
This commit is contained in:
parent
c1d34e430c
commit
4dac73ea9f
12 changed files with 469 additions and 282 deletions
|
|
@ -17,6 +17,11 @@ sealed class Task(priority: Int = DEFAULT_PRIORITY) : Serializable {
|
||||||
override fun uniqueId() = "SCAN_LIBRARY_$libraryId"
|
override fun uniqueId() = "SCAN_LIBRARY_$libraryId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyTrash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
||||||
|
override fun uniqueId() = "EMPTY_TRASH_$libraryId"
|
||||||
|
override fun toString(): String = "EmptyTrash(libraryId='$libraryId', priority='$priority')"
|
||||||
|
}
|
||||||
|
|
||||||
class AnalyzeBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
class AnalyzeBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
||||||
override fun uniqueId() = "ANALYZE_BOOK_$bookId"
|
override fun uniqueId() = "ANALYZE_BOOK_$bookId"
|
||||||
override fun toString(): String = "AnalyzeBook(bookId='$bookId', priority='$priority')"
|
override fun toString(): String = "AnalyzeBook(bookId='$bookId', priority='$priority')"
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@ class TaskHandler(
|
||||||
if (library.convertToCbz) taskReceiver.convertBooksToCbz(library, LOWEST_PRIORITY)
|
if (library.convertToCbz) taskReceiver.convertBooksToCbz(library, LOWEST_PRIORITY)
|
||||||
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
|
is Task.EmptyTrash ->
|
||||||
|
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
||||||
|
libraryContentLifecycle.emptyTrash(library)
|
||||||
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
is Task.AnalyzeBook ->
|
is Task.AnalyzeBook ->
|
||||||
bookRepository.findByIdOrNull(task.bookId)?.let { book ->
|
bookRepository.findByIdOrNull(task.bookId)?.let { book ->
|
||||||
if (bookLifecycle.analyzeAndPersist(book)) {
|
if (bookLifecycle.analyzeAndPersist(book)) {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ class TaskReceiver(
|
||||||
submitTask(Task.ScanLibrary(libraryId))
|
submitTask(Task.ScanLibrary(libraryId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun emptyTrash(libraryId: String, priority: Int = DEFAULT_PRIORITY) {
|
||||||
|
submitTask(Task.EmptyTrash(libraryId, priority))
|
||||||
|
}
|
||||||
|
|
||||||
fun analyzeUnknownAndOutdatedBooks(library: Library) {
|
fun analyzeUnknownAndOutdatedBooks(library: Library) {
|
||||||
bookRepository.findAllIds(
|
bookRepository.findAllIds(
|
||||||
BookSearch(
|
BookSearch(
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ interface ReadListRepository {
|
||||||
*/
|
*/
|
||||||
fun findAllContainingBookId(containsBookId: String, filterOnLibraryIds: Collection<String>?): Collection<ReadList>
|
fun findAllContainingBookId(containsBookId: String, filterOnLibraryIds: Collection<String>?): Collection<ReadList>
|
||||||
|
|
||||||
|
fun findAllEmpty(): Collection<ReadList>
|
||||||
|
|
||||||
fun findByNameOrNull(name: String): ReadList?
|
fun findByNameOrNull(name: String): ReadList?
|
||||||
|
|
||||||
fun insert(readList: ReadList)
|
fun insert(readList: ReadList)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ interface SeriesCollectionRepository {
|
||||||
*/
|
*/
|
||||||
fun findAllContainingSeriesId(containsSeriesId: String, filterOnLibraryIds: Collection<String>?): Collection<SeriesCollection>
|
fun findAllContainingSeriesId(containsSeriesId: String, filterOnLibraryIds: Collection<String>?): Collection<SeriesCollection>
|
||||||
|
|
||||||
|
fun findAllEmpty(): Collection<SeriesCollection>
|
||||||
|
|
||||||
fun findByNameOrNull(name: String): SeriesCollection?
|
fun findByNameOrNull(name: String): SeriesCollection?
|
||||||
|
|
||||||
fun insert(collection: SeriesCollection)
|
fun insert(collection: SeriesCollection)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ package org.gotson.komga.domain.service
|
||||||
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.gotson.komga.application.tasks.TaskReceiver
|
import org.gotson.komga.application.tasks.TaskReceiver
|
||||||
|
import org.gotson.komga.domain.model.BookSearch
|
||||||
import org.gotson.komga.domain.model.Library
|
import org.gotson.komga.domain.model.Library
|
||||||
import org.gotson.komga.domain.model.Media
|
import org.gotson.komga.domain.model.Media
|
||||||
|
import org.gotson.komga.domain.model.SeriesSearch
|
||||||
import org.gotson.komga.domain.model.Sidecar
|
import org.gotson.komga.domain.model.Sidecar
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.MediaRepository
|
import org.gotson.komga.domain.persistence.MediaRepository
|
||||||
import org.gotson.komga.domain.persistence.ReadListRepository
|
|
||||||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
|
||||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
import org.gotson.komga.domain.persistence.SidecarRepository
|
import org.gotson.komga.domain.persistence.SidecarRepository
|
||||||
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
||||||
|
|
@ -28,8 +28,8 @@ class LibraryContentLifecycle(
|
||||||
private val bookLifecycle: BookLifecycle,
|
private val bookLifecycle: BookLifecycle,
|
||||||
private val mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
private val seriesLifecycle: SeriesLifecycle,
|
private val seriesLifecycle: SeriesLifecycle,
|
||||||
private val collectionRepository: SeriesCollectionRepository,
|
private val collectionLifecycle: SeriesCollectionLifecycle,
|
||||||
private val readListRepository: ReadListRepository,
|
private val readListLifecycle: ReadListLifecycle,
|
||||||
private val sidecarRepository: SidecarRepository,
|
private val sidecarRepository: SidecarRepository,
|
||||||
private val komgaProperties: KomgaProperties,
|
private val komgaProperties: KomgaProperties,
|
||||||
private val taskReceiver: TaskReceiver,
|
private val taskReceiver: TaskReceiver,
|
||||||
|
|
@ -161,15 +161,32 @@ class LibraryContentLifecycle(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (komgaProperties.deleteEmptyCollections) {
|
cleanupEmptySets()
|
||||||
logger.info { "Deleting empty collections" }
|
|
||||||
collectionRepository.deleteEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (komgaProperties.deleteEmptyReadLists) {
|
|
||||||
logger.info { "Deleting empty read lists" }
|
|
||||||
readListRepository.deleteEmpty()
|
|
||||||
}
|
|
||||||
}.also { logger.info { "Library updated in $it" } }
|
}.also { logger.info { "Library updated in $it" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun emptyTrash(library: Library) {
|
||||||
|
logger.info { "Empty trash for library: $library" }
|
||||||
|
|
||||||
|
val seriesToDelete = seriesRepository.findAll(SeriesSearch(deleted = true))
|
||||||
|
seriesLifecycle.deleteMany(seriesToDelete)
|
||||||
|
|
||||||
|
val booksToDelete = bookRepository.findAll(BookSearch(deleted = true))
|
||||||
|
bookLifecycle.deleteMany(booksToDelete)
|
||||||
|
booksToDelete.map { it.seriesId }.distinct().forEach { seriesId ->
|
||||||
|
seriesRepository.findByIdOrNull(seriesId)?.let { seriesLifecycle.sortBooks(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupEmptySets()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupEmptySets() {
|
||||||
|
if (komgaProperties.deleteEmptyCollections) {
|
||||||
|
collectionLifecycle.deleteEmptyCollections()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (komgaProperties.deleteEmptyReadLists) {
|
||||||
|
readListLifecycle.deleteEmptyReadLists()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,13 @@ class ReadListLifecycle(
|
||||||
eventPublisher.publishEvent(DomainEvent.ReadListDeleted(readList))
|
eventPublisher.publishEvent(DomainEvent.ReadListDeleted(readList))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteEmptyReadLists() {
|
||||||
|
logger.info { "Deleting empty read lists" }
|
||||||
|
val toDelete = readListRepository.findAllEmpty()
|
||||||
|
readListRepository.deleteEmpty()
|
||||||
|
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.ReadListDeleted(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
fun getThumbnailBytes(readList: ReadList): ByteArray {
|
fun getThumbnailBytes(readList: ReadList): ByteArray {
|
||||||
val ids = with(mutableListOf<String>()) {
|
val ids = with(mutableListOf<String>()) {
|
||||||
while (size < 4) {
|
while (size < 4) {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,13 @@ class SeriesCollectionLifecycle(
|
||||||
eventPublisher.publishEvent(DomainEvent.CollectionDeleted(collection))
|
eventPublisher.publishEvent(DomainEvent.CollectionDeleted(collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteEmptyCollections() {
|
||||||
|
logger.info { "Deleting empty collections" }
|
||||||
|
val toDelete = collectionRepository.findAllEmpty()
|
||||||
|
collectionRepository.deleteEmpty()
|
||||||
|
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.CollectionDeleted(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
fun getThumbnailBytes(collection: SeriesCollection): ByteArray {
|
fun getThumbnailBytes(collection: SeriesCollection): ByteArray {
|
||||||
val ids = with(mutableListOf<String>()) {
|
val ids = with(mutableListOf<String>()) {
|
||||||
while (size < 4) {
|
while (size < 4) {
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,18 @@ class ReadListDao(
|
||||||
.fetchAndMap(filterOnLibraryIds)
|
.fetchAndMap(filterOnLibraryIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findAllEmpty(): Collection<ReadList> =
|
||||||
|
dsl.selectFrom(rl)
|
||||||
|
.where(
|
||||||
|
rl.ID.`in`(
|
||||||
|
dsl.select(rl.ID)
|
||||||
|
.from(rl)
|
||||||
|
.leftJoin(rlb).on(rl.ID.eq(rlb.READLIST_ID))
|
||||||
|
.where(rlb.READLIST_ID.isNull)
|
||||||
|
)
|
||||||
|
).fetchInto(rl)
|
||||||
|
.map { it.toDomain(sortedMapOf()) }
|
||||||
|
|
||||||
override fun findByNameOrNull(name: String): ReadList? =
|
override fun findByNameOrNull(name: String): ReadList? =
|
||||||
selectBase()
|
selectBase()
|
||||||
.where(rl.NAME.equalIgnoreCase(name))
|
.where(rl.NAME.equalIgnoreCase(name))
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,18 @@ class SeriesCollectionDao(
|
||||||
.fetchAndMap(filterOnLibraryIds)
|
.fetchAndMap(filterOnLibraryIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findAllEmpty(): Collection<SeriesCollection> =
|
||||||
|
dsl.selectFrom(c)
|
||||||
|
.where(
|
||||||
|
c.ID.`in`(
|
||||||
|
dsl.select(c.ID)
|
||||||
|
.from(c)
|
||||||
|
.leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID))
|
||||||
|
.where(cs.COLLECTION_ID.isNull)
|
||||||
|
)
|
||||||
|
).fetchInto(c)
|
||||||
|
.map { it.toDomain(emptyList()) }
|
||||||
|
|
||||||
override fun findByNameOrNull(name: String): SeriesCollection? =
|
override fun findByNameOrNull(name: String): SeriesCollection? =
|
||||||
selectBase()
|
selectBase()
|
||||||
.where(c.NAME.equalIgnoreCase(name))
|
.where(c.NAME.equalIgnoreCase(name))
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,15 @@ class LibraryController(
|
||||||
taskReceiver.refreshSeriesLocalArtwork(it, priority = HIGH_PRIORITY)
|
taskReceiver.refreshSeriesLocalArtwork(it, priority = HIGH_PRIORITY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("{libraryId}/empty-trash")
|
||||||
|
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||||
|
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||||
|
fun emptyTrash(@PathVariable libraryId: String) {
|
||||||
|
libraryRepository.findByIdOrNull(libraryId)?.let { library ->
|
||||||
|
taskReceiver.emptyTrash(library.id, HIGH_PRIORITY)
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LibraryCreationDto(
|
data class LibraryCreationDto(
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ import io.mockk.verify
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.gotson.komga.domain.model.Book
|
import org.gotson.komga.domain.model.Book
|
||||||
import org.gotson.komga.domain.model.Media
|
import org.gotson.komga.domain.model.Media
|
||||||
|
import org.gotson.komga.domain.model.ReadList
|
||||||
import org.gotson.komga.domain.model.ScanResult
|
import org.gotson.komga.domain.model.ScanResult
|
||||||
import org.gotson.komga.domain.model.Series
|
import org.gotson.komga.domain.model.Series
|
||||||
|
import org.gotson.komga.domain.model.SeriesCollection
|
||||||
import org.gotson.komga.domain.model.makeBook
|
import org.gotson.komga.domain.model.makeBook
|
||||||
import org.gotson.komga.domain.model.makeBookPage
|
import org.gotson.komga.domain.model.makeBookPage
|
||||||
import org.gotson.komga.domain.model.makeLibrary
|
import org.gotson.komga.domain.model.makeLibrary
|
||||||
|
|
@ -15,13 +17,18 @@ import org.gotson.komga.domain.model.makeSeries
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||||
import org.gotson.komga.domain.persistence.MediaRepository
|
import org.gotson.komga.domain.persistence.MediaRepository
|
||||||
|
import org.gotson.komga.domain.persistence.ReadListRepository
|
||||||
|
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
import org.gotson.komga.infrastructure.hash.Hasher
|
import org.gotson.komga.infrastructure.hash.Hasher
|
||||||
|
import org.gotson.komga.infrastructure.language.toIndexedMap
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
@ -34,7 +41,9 @@ class LibraryContentLifecycleTest(
|
||||||
@Autowired private val libraryContentLifecycle: LibraryContentLifecycle,
|
@Autowired private val libraryContentLifecycle: LibraryContentLifecycle,
|
||||||
@Autowired private val bookLifecycle: BookLifecycle,
|
@Autowired private val bookLifecycle: BookLifecycle,
|
||||||
@Autowired private val mediaRepository: MediaRepository,
|
@Autowired private val mediaRepository: MediaRepository,
|
||||||
@Autowired private val libraryLifecycle: LibraryLifecycle
|
@Autowired private val libraryLifecycle: LibraryLifecycle,
|
||||||
|
@Autowired private val collectionRepository: SeriesCollectionRepository,
|
||||||
|
@Autowired private val readListRepository: ReadListRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@MockkBean
|
@MockkBean
|
||||||
|
|
@ -56,348 +65,444 @@ class LibraryContentLifecycleTest(
|
||||||
private fun Map<Series, List<Book>>.toScanResult() =
|
private fun Map<Series, List<Book>>.toScanResult() =
|
||||||
ScanResult(this, emptyList())
|
ScanResult(this, emptyList())
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
fun `given existing series when adding files and scanning then only updated Books are persisted`() {
|
inner class Scan {
|
||||||
// given
|
@Test
|
||||||
val library = makeLibrary()
|
fun `given existing series when adding files and scanning then only updated Books are persisted`() {
|
||||||
libraryRepository.insert(library)
|
// given
|
||||||
|
val library = makeLibrary()
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
val books = listOf(makeBook("book1"))
|
val books = listOf(makeBook("book1"))
|
||||||
val moreBooks = listOf(makeBook("book1"), makeBook("book2"))
|
val moreBooks = listOf(makeBook("book1"), makeBook("book2"))
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }.returnsMany(
|
every { mockScanner.scanRootFolder(any()) }.returnsMany(
|
||||||
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
|
||||||
mapOf(makeSeries(name = "series") to moreBooks).toScanResult(),
|
|
||||||
)
|
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
|
||||||
|
|
||||||
// when
|
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
|
||||||
|
|
||||||
// then
|
|
||||||
val allSeries = seriesRepository.findAll()
|
|
||||||
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
|
||||||
|
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
|
||||||
|
|
||||||
assertThat(allSeries).hasSize(1)
|
|
||||||
assertThat(allBooks).hasSize(2)
|
|
||||||
assertThat(allBooks.map { it.name }).containsExactly("book1", "book2")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given existing series when removing files and scanning then updated Books are persisted and removed books are marked as such`() {
|
|
||||||
// given
|
|
||||||
val library = makeLibrary()
|
|
||||||
libraryRepository.insert(library)
|
|
||||||
|
|
||||||
val books = listOf(makeBook("book1"), makeBook("book2"))
|
|
||||||
val lessBooks = listOf(makeBook("book1"))
|
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
|
||||||
.returnsMany(
|
|
||||||
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
||||||
mapOf(makeSeries(name = "series") to lessBooks).toScanResult(),
|
mapOf(makeSeries(name = "series") to moreBooks).toScanResult(),
|
||||||
)
|
)
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val allSeries = seriesRepository.findAll()
|
val allSeries = seriesRepository.findAll()
|
||||||
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
||||||
|
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
|
|
||||||
assertThat(allSeries).hasSize(1)
|
assertThat(allSeries).hasSize(1)
|
||||||
assertThat(allBooks).hasSize(2)
|
assertThat(allBooks).hasSize(2)
|
||||||
assertThat(allBooks.filter { it.deletedDate == null }.map { it.name }).containsExactly("book1")
|
assertThat(allBooks.map { it.name }).containsExactly("book1", "book2")
|
||||||
assertThat(allBooks.filter { it.deletedDate != null }.map { it.name }).containsExactly("book2")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing series when removing files and scanning, restoring files and scanning then restored books are available`() {
|
fun `given existing series when removing files and scanning then updated Books are persisted and removed books are marked as such`() {
|
||||||
// given
|
// given
|
||||||
val library = makeLibrary()
|
val library = makeLibrary()
|
||||||
libraryRepository.insert(library)
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
val books = listOf(makeBook("book1"), makeBook("book2"))
|
val books = listOf(makeBook("book1"), makeBook("book2"))
|
||||||
val lessBooks = listOf(makeBook("book1"))
|
val lessBooks = listOf(makeBook("book1"))
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
.returnsMany(
|
.returnsMany(
|
||||||
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
||||||
mapOf(makeSeries(name = "series") to lessBooks).toScanResult(),
|
mapOf(makeSeries(name = "series") to lessBooks).toScanResult(),
|
||||||
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
)
|
||||||
)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
libraryContentLifecycle.scanRootFolder(library) // creation
|
|
||||||
libraryContentLifecycle.scanRootFolder(library) // deletion
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
libraryContentLifecycle.scanRootFolder(library) // restore
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val allSeries = seriesRepository.findAll()
|
val allSeries = seriesRepository.findAll()
|
||||||
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
||||||
|
|
||||||
verify(exactly = 3) { mockScanner.scanRootFolder(any()) }
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
|
|
||||||
assertThat(allSeries).hasSize(1)
|
assertThat(allSeries).hasSize(1)
|
||||||
assertThat(allBooks).hasSize(2)
|
assertThat(allBooks).hasSize(2)
|
||||||
assertThat(allBooks.map { it.deletedDate }).containsOnlyNulls()
|
assertThat(allBooks.filter { it.deletedDate == null }.map { it.name }).containsExactly("book1")
|
||||||
}
|
assertThat(allBooks.filter { it.deletedDate != null }.map { it.name }).containsExactly("book2")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing series when updating files and scanning then Books are updated`() {
|
fun `given existing series when removing files and scanning, restoring files and scanning then restored books are available`() {
|
||||||
// given
|
// given
|
||||||
val library = makeLibrary()
|
val library = makeLibrary()
|
||||||
libraryRepository.insert(library)
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
val books = listOf(makeBook("book1"))
|
val books = listOf(makeBook("book1"), makeBook("book2"))
|
||||||
val updatedBooks = listOf(makeBook("book1"))
|
val lessBooks = listOf(makeBook("book1"))
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
.returnsMany(
|
.returnsMany(
|
||||||
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
||||||
mapOf(makeSeries(name = "series") to updatedBooks).toScanResult(),
|
mapOf(makeSeries(name = "series") to lessBooks).toScanResult(),
|
||||||
)
|
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library) // creation
|
||||||
|
libraryContentLifecycle.scanRootFolder(library) // deletion
|
||||||
|
|
||||||
// when
|
// when
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library) // restore
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val allSeries = seriesRepository.findAll()
|
val allSeries = seriesRepository.findAll()
|
||||||
val allBooks = bookRepository.findAll()
|
val allBooks = bookRepository.findAll().sortedBy { it.number }
|
||||||
|
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
verify(exactly = 3) { mockScanner.scanRootFolder(any()) }
|
||||||
|
|
||||||
assertThat(allSeries).hasSize(1)
|
assertThat(allSeries).hasSize(1)
|
||||||
assertThat(allBooks).hasSize(1)
|
assertThat(allBooks).hasSize(2)
|
||||||
assertThat(allBooks.map { it.name }).containsExactly("book1")
|
assertThat(allBooks.map { it.deletedDate }).containsOnlyNulls()
|
||||||
assertThat(allBooks.first().lastModifiedDate).isNotEqualTo(allBooks.first().createdDate)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing series when deleting all books and scanning then Series and Books are marked as deleted`() {
|
fun `given existing series when updating files and scanning then Books are updated`() {
|
||||||
// given
|
// given
|
||||||
val library = makeLibrary()
|
val library = makeLibrary()
|
||||||
libraryRepository.insert(library)
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
val books = listOf(makeBook("book1"))
|
||||||
.returnsMany(
|
val updatedBooks = listOf(makeBook("book1"))
|
||||||
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"))).toScanResult(),
|
|
||||||
emptyMap<Series, List<Book>>().toScanResult(),
|
|
||||||
)
|
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
|
||||||
|
|
||||||
// when
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series") to books).toScanResult(),
|
||||||
|
mapOf(makeSeries(name = "series") to updatedBooks).toScanResult(),
|
||||||
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// then
|
// when
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
val allSeries = seriesRepository.findAll()
|
// then
|
||||||
val allBooks = bookRepository.findAll()
|
val allSeries = seriesRepository.findAll()
|
||||||
|
val allBooks = bookRepository.findAll()
|
||||||
|
|
||||||
assertThat(allSeries.map { it.deletedDate }).doesNotContainNull()
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
assertThat(allSeries).hasSize(1)
|
|
||||||
assertThat(allBooks.map { it.deletedDate }).doesNotContainNull()
|
|
||||||
assertThat(allBooks).hasSize(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
assertThat(allSeries).hasSize(1)
|
||||||
fun `given existing series when deleting all books and scanning then restoring and scanning then Series and Books are available`() {
|
assertThat(allBooks).hasSize(1)
|
||||||
// given
|
assertThat(allBooks.map { it.name }).containsExactly("book1")
|
||||||
val library = makeLibrary()
|
assertThat(allBooks.first().lastModifiedDate).isNotEqualTo(allBooks.first().createdDate)
|
||||||
libraryRepository.insert(library)
|
}
|
||||||
|
|
||||||
val series = makeSeries(name = "series")
|
@Test
|
||||||
val book = makeBook("book1")
|
fun `given existing series when deleting all books and scanning then Series and Books are marked as deleted`() {
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
// given
|
||||||
.returnsMany(
|
val library = makeLibrary()
|
||||||
mapOf(series to listOf(book)).toScanResult(),
|
libraryRepository.insert(library)
|
||||||
emptyMap<Series, List<Book>>().toScanResult(),
|
|
||||||
mapOf(series to listOf(book)).toScanResult(),
|
|
||||||
)
|
|
||||||
libraryContentLifecycle.scanRootFolder(library) // creation
|
|
||||||
libraryContentLifecycle.scanRootFolder(library) // deletion
|
|
||||||
|
|
||||||
// when
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
libraryContentLifecycle.scanRootFolder(library) // restore
|
.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"))).toScanResult(),
|
||||||
|
emptyMap<Series, List<Book>>().toScanResult(),
|
||||||
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// then
|
// when
|
||||||
verify(exactly = 3) { mockScanner.scanRootFolder(any()) }
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
val allSeries = seriesRepository.findAll()
|
// then
|
||||||
val allBooks = bookRepository.findAll()
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
|
|
||||||
assertThat(allSeries.map { it.deletedDate }).containsOnlyNulls()
|
val allSeries = seriesRepository.findAll()
|
||||||
assertThat(allSeries).hasSize(1)
|
val allBooks = bookRepository.findAll()
|
||||||
assertThat(allBooks.map { it.deletedDate }).containsOnlyNulls()
|
|
||||||
assertThat(allBooks).hasSize(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
assertThat(allSeries.map { it.deletedDate }).doesNotContainNull()
|
||||||
fun `given existing Series when deleting all books of one series and scanning then series and its Books are marked as deleted`() {
|
assertThat(allSeries).hasSize(1)
|
||||||
// given
|
assertThat(allBooks.map { it.deletedDate }).doesNotContainNull()
|
||||||
val library = makeLibrary()
|
assertThat(allBooks).hasSize(1)
|
||||||
libraryRepository.insert(library)
|
}
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
@Test
|
||||||
.returnsMany(
|
fun `given existing series when deleting all books and scanning then restoring and scanning then Series and Books are available`() {
|
||||||
mapOf(
|
// given
|
||||||
makeSeries(name = "series") to listOf(makeBook("book1")),
|
val library = makeLibrary()
|
||||||
makeSeries(name = "series2") to listOf(makeBook("book2")),
|
libraryRepository.insert(library)
|
||||||
).toScanResult(),
|
|
||||||
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"))).toScanResult(),
|
|
||||||
)
|
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
|
||||||
|
|
||||||
// when
|
val series = makeSeries(name = "series")
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
val book = makeBook("book1")
|
||||||
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
|
.returnsMany(
|
||||||
|
mapOf(series to listOf(book)).toScanResult(),
|
||||||
|
emptyMap<Series, List<Book>>().toScanResult(),
|
||||||
|
mapOf(series to listOf(book)).toScanResult(),
|
||||||
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library) // creation
|
||||||
|
libraryContentLifecycle.scanRootFolder(library) // deletion
|
||||||
|
|
||||||
// then
|
// when
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
libraryContentLifecycle.scanRootFolder(library) // restore
|
||||||
|
|
||||||
val (series, deletedSeries) = seriesRepository.findAll().partition { it.deletedDate == null }
|
// then
|
||||||
val (books, deletedBooks) = bookRepository.findAll().partition { it.deletedDate == null }
|
verify(exactly = 3) { mockScanner.scanRootFolder(any()) }
|
||||||
|
|
||||||
assertThat(series).hasSize(1)
|
val allSeries = seriesRepository.findAll()
|
||||||
assertThat(series.map { it.name }).containsExactlyInAnyOrder("series")
|
val allBooks = bookRepository.findAll()
|
||||||
|
|
||||||
assertThat(deletedSeries).hasSize(1)
|
assertThat(allSeries.map { it.deletedDate }).containsOnlyNulls()
|
||||||
assertThat(deletedSeries.map { it.name }).containsExactlyInAnyOrder("series2")
|
assertThat(allSeries).hasSize(1)
|
||||||
|
assertThat(allBooks.map { it.deletedDate }).containsOnlyNulls()
|
||||||
|
assertThat(allBooks).hasSize(1)
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(books).hasSize(1)
|
@Test
|
||||||
assertThat(books.map { it.name }).containsExactlyInAnyOrder("book1")
|
fun `given existing Series when deleting all books of one series and scanning then series and its Books are marked as deleted`() {
|
||||||
|
// given
|
||||||
|
val library = makeLibrary()
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
assertThat(deletedBooks).hasSize(1)
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
assertThat(deletedBooks.map { it.name }).containsExactlyInAnyOrder("book2")
|
.returnsMany(
|
||||||
}
|
mapOf(
|
||||||
|
makeSeries(name = "series") to listOf(makeBook("book1")),
|
||||||
|
makeSeries(name = "series2") to listOf(makeBook("book2")),
|
||||||
|
).toScanResult(),
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"))).toScanResult(),
|
||||||
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
fun `given existing Book with media when rescanning then media is kept intact`() {
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
// given
|
|
||||||
val library = makeLibrary()
|
|
||||||
libraryRepository.insert(library)
|
|
||||||
|
|
||||||
val book1 = makeBook("book1")
|
// then
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
.returnsMany(
|
|
||||||
mapOf(makeSeries(name = "series") to listOf(book1)).toScanResult(),
|
|
||||||
mapOf(makeSeries(name = "series") to listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))).toScanResult(),
|
|
||||||
)
|
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
|
||||||
|
|
||||||
every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
|
val (series, deletedSeries) = seriesRepository.findAll().partition { it.deletedDate == null }
|
||||||
bookRepository.findAll().map { bookLifecycle.analyzeAndPersist(it) }
|
val (books, deletedBooks) = bookRepository.findAll().partition { it.deletedDate == null }
|
||||||
|
|
||||||
// when
|
assertThat(series).hasSize(1)
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
assertThat(series.map { it.name }).containsExactlyInAnyOrder("series")
|
||||||
|
|
||||||
// then
|
assertThat(deletedSeries).hasSize(1)
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
assertThat(deletedSeries.map { it.name }).containsExactlyInAnyOrder("series2")
|
||||||
verify(exactly = 1) { mockAnalyzer.analyze(any()) }
|
|
||||||
|
|
||||||
bookRepository.findAll().first().let { book ->
|
assertThat(books).hasSize(1)
|
||||||
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
|
assertThat(books.map { it.name }).containsExactlyInAnyOrder("book1")
|
||||||
|
|
||||||
mediaRepository.findById(book.id).let { media ->
|
assertThat(deletedBooks).hasSize(1)
|
||||||
assertThat(media.status).isEqualTo(Media.Status.READY)
|
assertThat(deletedBooks.map { it.name }).containsExactlyInAnyOrder("book2")
|
||||||
assertThat(media.mediaType).isEqualTo("application/zip")
|
}
|
||||||
assertThat(media.pages).hasSize(2)
|
|
||||||
assertThat(media.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
|
@Test
|
||||||
|
fun `given existing Book with media when rescanning then media is kept intact`() {
|
||||||
|
// given
|
||||||
|
val library = makeLibrary()
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
|
val book1 = makeBook("book1")
|
||||||
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
|
.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(book1)).toScanResult(),
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))).toScanResult(),
|
||||||
|
)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
|
every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
|
||||||
|
bookRepository.findAll().map { bookLifecycle.analyzeAndPersist(it) }
|
||||||
|
|
||||||
|
// when
|
||||||
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
|
verify(exactly = 1) { mockAnalyzer.analyze(any()) }
|
||||||
|
|
||||||
|
bookRepository.findAll().first().let { book ->
|
||||||
|
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
|
||||||
|
|
||||||
|
mediaRepository.findById(book.id).let { media ->
|
||||||
|
assertThat(media.status).isEqualTo(Media.Status.READY)
|
||||||
|
assertThat(media.mediaType).isEqualTo("application/zip")
|
||||||
|
assertThat(media.pages).hasSize(2)
|
||||||
|
assertThat(media.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing Book with different last modified date when rescanning then media is marked as outdated and hash is reset`() {
|
fun `given existing Book with different last modified date when rescanning then media is marked as outdated and hash is reset`() {
|
||||||
// given
|
// given
|
||||||
val library = makeLibrary()
|
val library = makeLibrary()
|
||||||
libraryRepository.insert(library)
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
val book1 = makeBook("book1")
|
val book1 = makeBook("book1")
|
||||||
every { mockScanner.scanRootFolder(any()) }
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
.returnsMany(
|
.returnsMany(
|
||||||
mapOf(makeSeries(name = "series") to listOf(book1)).toScanResult(),
|
mapOf(makeSeries(name = "series") to listOf(book1)).toScanResult(),
|
||||||
mapOf(makeSeries(name = "series") to listOf(makeBook(name = "book1"))).toScanResult(),
|
mapOf(makeSeries(name = "series") to listOf(makeBook(name = "book1"))).toScanResult(),
|
||||||
)
|
)
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
|
every { mockAnalyzer.analyze(any()) } returns Media(status = Media.Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")), bookId = book1.id)
|
||||||
every { mockHasher.computeHash(any()) }.returnsMany("abc", "def")
|
every { mockHasher.computeHash(any()) }.returnsMany("abc", "def")
|
||||||
|
|
||||||
bookRepository.findAll().map {
|
bookRepository.findAll().map {
|
||||||
bookLifecycle.analyzeAndPersist(it)
|
bookLifecycle.analyzeAndPersist(it)
|
||||||
bookLifecycle.hashAndPersist(it)
|
bookLifecycle.hashAndPersist(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
|
||||||
verify(exactly = 1) { mockAnalyzer.analyze(any()) }
|
verify(exactly = 1) { mockAnalyzer.analyze(any()) }
|
||||||
verify(exactly = 1) { mockHasher.computeHash(any()) }
|
verify(exactly = 1) { mockHasher.computeHash(any()) }
|
||||||
|
|
||||||
bookRepository.findAll().first().let { book ->
|
bookRepository.findAll().first().let { book ->
|
||||||
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
|
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
|
||||||
assertThat(book.fileHash).isEmpty()
|
assertThat(book.fileHash).isEmpty()
|
||||||
|
|
||||||
mediaRepository.findById(book.id).let { media ->
|
mediaRepository.findById(book.id).let { media ->
|
||||||
assertThat(media.status).isEqualTo(Media.Status.OUTDATED)
|
assertThat(media.status).isEqualTo(Media.Status.OUTDATED)
|
||||||
assertThat(media.mediaType).isEqualTo("application/zip")
|
assertThat(media.mediaType).isEqualTo("application/zip")
|
||||||
assertThat(media.pages).hasSize(2)
|
assertThat(media.pages).hasSize(2)
|
||||||
assertThat(media.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
|
assertThat(media.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given 2 libraries when deleting all books of one and scanning then the other library is kept intact`() {
|
||||||
|
// given
|
||||||
|
val library1 = makeLibrary(name = "library1")
|
||||||
|
libraryRepository.insert(library1)
|
||||||
|
val library2 = makeLibrary(name = "library2")
|
||||||
|
libraryRepository.insert(library2)
|
||||||
|
|
||||||
|
every { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) } returns
|
||||||
|
mapOf(makeSeries(name = "series1") to listOf(makeBook("book1"))).toScanResult()
|
||||||
|
|
||||||
|
every { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series2") to listOf(makeBook("book2"))).toScanResult(),
|
||||||
|
emptyMap<Series, List<Book>>().toScanResult(),
|
||||||
|
)
|
||||||
|
|
||||||
|
libraryContentLifecycle.scanRootFolder(library1)
|
||||||
|
libraryContentLifecycle.scanRootFolder(library2)
|
||||||
|
|
||||||
|
assertThat(seriesRepository.count()).describedAs("Series repository should not be empty").isEqualTo(2)
|
||||||
|
assertThat(bookRepository.count()).describedAs("Book repository should not be empty").isEqualTo(2)
|
||||||
|
|
||||||
|
// when
|
||||||
|
libraryContentLifecycle.scanRootFolder(library2)
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(exactly = 1) { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) }
|
||||||
|
verify(exactly = 2) { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }
|
||||||
|
|
||||||
|
val (seriesLib1, seriesLib2) = seriesRepository.findAll().partition { it.libraryId == library1.id }
|
||||||
|
val (booksLib1, booksLib2) = bookRepository.findAll().partition { it.libraryId == library1.id }
|
||||||
|
|
||||||
|
assertThat(seriesLib1.map { it.deletedDate }).containsOnlyNulls()
|
||||||
|
assertThat(seriesLib1.map { it.name }).containsExactlyInAnyOrder("series1")
|
||||||
|
|
||||||
|
assertThat(seriesLib2.map { it.deletedDate }).doesNotContainNull()
|
||||||
|
assertThat(seriesLib2.map { it.name }).containsExactlyInAnyOrder("series2")
|
||||||
|
|
||||||
|
assertThat(booksLib1.map { it.deletedDate }).containsOnlyNulls()
|
||||||
|
assertThat(booksLib1.map { it.name }).containsExactlyInAnyOrder("book1")
|
||||||
|
|
||||||
|
assertThat(booksLib2.map { it.deletedDate }).doesNotContainNull()
|
||||||
|
assertThat(booksLib2.map { it.name }).containsExactlyInAnyOrder("book2")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
fun `given 2 libraries when deleting all books of one and scanning then the other library is kept intact`() {
|
inner class EmptyTrash {
|
||||||
// given
|
@Test
|
||||||
val library1 = makeLibrary(name = "library1")
|
fun `given library with deleted series and books when emptying the trash then deleted elements are permanently removed`() {
|
||||||
libraryRepository.insert(library1)
|
// given
|
||||||
val library2 = makeLibrary(name = "library2")
|
val library = makeLibrary()
|
||||||
libraryRepository.insert(library2)
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) } returns
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
mapOf(makeSeries(name = "series1") to listOf(makeBook("book1"))).toScanResult()
|
.returnsMany(
|
||||||
|
mapOf(
|
||||||
|
makeSeries(name = "series") to listOf(makeBook("book1"), makeBook("book3")),
|
||||||
|
makeSeries(name = "series2") to listOf(makeBook("book2")),
|
||||||
|
).toScanResult(),
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"))).toScanResult(),
|
||||||
|
)
|
||||||
|
repeat(2) { libraryContentLifecycle.scanRootFolder(library) }
|
||||||
|
|
||||||
every { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }.returnsMany(
|
// when
|
||||||
mapOf(makeSeries(name = "series2") to listOf(makeBook("book2"))).toScanResult(),
|
libraryContentLifecycle.emptyTrash(library)
|
||||||
emptyMap<Series, List<Book>>().toScanResult(),
|
|
||||||
)
|
|
||||||
|
|
||||||
libraryContentLifecycle.scanRootFolder(library1)
|
// then
|
||||||
libraryContentLifecycle.scanRootFolder(library2)
|
val (series, deletedSeries) = seriesRepository.findAll().partition { it.deletedDate == null }
|
||||||
|
val (books, deletedBooks) = bookRepository.findAll().partition { it.deletedDate == null }
|
||||||
|
|
||||||
assertThat(seriesRepository.count()).describedAs("Series repository should not be empty").isEqualTo(2)
|
assertThat(series).hasSize(1)
|
||||||
assertThat(bookRepository.count()).describedAs("Book repository should not be empty").isEqualTo(2)
|
assertThat(series.map { it.name }).containsExactlyInAnyOrder("series")
|
||||||
|
assertThat(deletedSeries).isEmpty()
|
||||||
|
|
||||||
// when
|
assertThat(books).hasSize(1)
|
||||||
libraryContentLifecycle.scanRootFolder(library2)
|
assertThat(books.map { it.name }).containsExactlyInAnyOrder("book1")
|
||||||
|
assertThat(deletedBooks).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
verify(exactly = 1) { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) }
|
fun `given series with books when emptying the trash then the series is properly sorted`() {
|
||||||
verify(exactly = 2) { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }
|
// given
|
||||||
|
val library = makeLibrary()
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
val (seriesLib1, seriesLib2) = seriesRepository.findAll().partition { it.libraryId == library1.id }
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
val (booksLib1, booksLib2) = bookRepository.findAll().partition { it.libraryId == library1.id }
|
.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"), makeBook("book2"), makeBook("book3"))).toScanResult(),
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book2"), makeBook("book3"))).toScanResult(),
|
||||||
|
)
|
||||||
|
repeat(2) { libraryContentLifecycle.scanRootFolder(library) }
|
||||||
|
|
||||||
assertThat(seriesLib1.map { it.deletedDate }).containsOnlyNulls()
|
// when
|
||||||
assertThat(seriesLib1.map { it.name }).containsExactlyInAnyOrder("series1")
|
libraryContentLifecycle.emptyTrash(library)
|
||||||
|
|
||||||
assertThat(seriesLib2.map { it.deletedDate }).doesNotContainNull()
|
// then
|
||||||
assertThat(seriesLib2.map { it.name }).containsExactlyInAnyOrder("series2")
|
val series = seriesRepository.findAll().first()
|
||||||
|
val books = bookRepository.findAllBySeriesId(series.id).sortedBy { it.number }
|
||||||
|
|
||||||
assertThat(booksLib1.map { it.deletedDate }).containsOnlyNulls()
|
assertThat(books).hasSize(2)
|
||||||
assertThat(booksLib1.map { it.name }).containsExactlyInAnyOrder("book1")
|
with(books.first()) {
|
||||||
|
assertThat(name).isEqualTo("book2")
|
||||||
|
assertThat(number).isEqualTo(1)
|
||||||
|
}
|
||||||
|
with(books.last()) {
|
||||||
|
assertThat(name).isEqualTo("book3")
|
||||||
|
assertThat(number).isEqualTo(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(booksLib2.map { it.deletedDate }).doesNotContainNull()
|
@Test
|
||||||
assertThat(booksLib2.map { it.name }).containsExactlyInAnyOrder("book2")
|
fun `given collection and read list with deleted elements when emptying the trash then those sets are deleted`() {
|
||||||
|
// given
|
||||||
|
val library = makeLibrary()
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
|
||||||
|
every { mockScanner.scanRootFolder(any()) }
|
||||||
|
.returnsMany(
|
||||||
|
mapOf(makeSeries(name = "series") to listOf(makeBook("book1"), makeBook("book2"), makeBook("book3"))).toScanResult(),
|
||||||
|
emptyMap<Series, List<Book>>().toScanResult(),
|
||||||
|
)
|
||||||
|
repeat(2) { libraryContentLifecycle.scanRootFolder(library) }
|
||||||
|
|
||||||
|
collectionRepository.insert(SeriesCollection("collection", seriesIds = seriesRepository.findAllIdsByLibraryId(library.id).toList()))
|
||||||
|
readListRepository.insert(ReadList("readlist", bookIds = bookRepository.findAllIdsByLibraryId(library.id).toList().toIndexedMap()))
|
||||||
|
|
||||||
|
// when
|
||||||
|
libraryContentLifecycle.emptyTrash(library)
|
||||||
|
|
||||||
|
// then
|
||||||
|
val collections = collectionRepository.searchAll(null, Pageable.unpaged())
|
||||||
|
val readLists = readListRepository.searchAll(null, Pageable.unpaged())
|
||||||
|
|
||||||
|
assertThat(collections.content).isEmpty()
|
||||||
|
assertThat(readLists.content).isEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue