feat: identify duplicate pages by hash only

remove the matching on mediaType and file size
remove extraneous database columns
remove constraint for auto-delete hash to have a size
This commit is contained in:
Gauthier Roebroeck 2023-06-27 11:41:03 +08:00
parent a03bda9797
commit 2d95679dd4
23 changed files with 88 additions and 182 deletions

View file

@ -26,7 +26,6 @@
<v-row>
<v-col>
<div>{{ hash.mediaType }}</div>
<div>{{ getFileSize(hash.size) || $t('duplicate_pages.unknown_size') }}</div>
</v-col>
</v-row>

View file

@ -18,7 +18,6 @@
<v-container>
<v-row>
<v-col>
<div>{{ hash.mediaType }}</div>
<div>{{ getFileSize(hash.size) || $t('duplicate_pages.unknown_size') }}</div>
</v-col>
</v-row>
@ -92,7 +91,6 @@ export default Vue.extend({
try {
await this.$komgaPageHashes.createOrUpdatePageHash({
hash: this.hash.hash,
mediaType: this.hash.mediaType,
size: this.hash.size,
action: action,
})

View file

@ -75,13 +75,13 @@ export function transientBookPageUrl(transientBookId: string, page: number): str
}
export function pageHashUnknownThumbnailUrl(pageHash: PageHashUnknownDto, resize?: number): string {
let url = `${urls.originNoSlash}/api/v1/page-hashes/unknown/${pageHash.hash}/thumbnail?media_type=${pageHash.mediaType}&file_size=${pageHash.size || -1}`
let url = `${urls.originNoSlash}/api/v1/page-hashes/unknown/${pageHash.hash}/thumbnail`
if(resize) {
url += `&resize=${resize}`
url += `?resize=${resize}`
}
return url
}
export function pageHashKnownThumbnailUrl(pageHash: PageHashKnownDto): string {
return `${urls.originNoSlash}/api/v1/page-hashes/${pageHash.hash}/thumbnail?media_type=${pageHash.mediaType}&file_size=${pageHash.size || -1}`
return `${urls.originNoSlash}/api/v1/page-hashes/${pageHash.hash}/thumbnail`
}

View file

@ -52,13 +52,8 @@ export default class KomgaPageHashesService {
async getPageHashMatches(pageHash: PageHashDto, pageRequest?: PageRequest): Promise<Page<PageHashMatchDto>> {
try {
const params = {
...pageRequest,
media_type: pageHash.mediaType,
file_size: pageHash.size || -1,
}
return (await this.http.get(`${API_PAGE_HASH}/${pageHash.hash}`, {
params: params,
params: pageRequest,
paramsSerializer: params => qs.stringify(params, {indices: false}),
})).data
} catch (e) {
@ -84,14 +79,7 @@ export default class KomgaPageHashesService {
async deleteAllMatches(pageHash: PageHashDto) {
try {
const params = {
media_type: pageHash.mediaType,
file_size: pageHash.size || -1,
}
await this.http.post(`${API_PAGE_HASH}/${pageHash.hash}/delete-all`, null, {
params: params,
paramsSerializer: params => qs.stringify(params, {indices: false}),
})
await this.http.post(`${API_PAGE_HASH}/${pageHash.hash}/delete-all`)
} catch (e) {
let msg = `An error occurred while trying to execute delete all matches on page hash ${pageHash}`
if (e.response.data.message) {
@ -103,14 +91,7 @@ export default class KomgaPageHashesService {
async deleteSingleMatch(pageHash: PageHashDto, match: PageHashMatchDto) {
try {
const params = {
media_type: pageHash.mediaType,
file_size: pageHash.size || -1,
}
await this.http.post(`${API_PAGE_HASH}/${pageHash.hash}/delete-match`, match, {
params: params,
paramsSerializer: params => qs.stringify(params, {indices: false}),
})
await this.http.post(`${API_PAGE_HASH}/${pageHash.hash}/delete-match`, match)
} catch (e) {
let msg = `An error occurred while trying to execute delete single match on page hash ${pageHash}`
if (e.response.data.message) {

View file

@ -2,7 +2,6 @@ import {PageHashAction} from '@/types/enum-pagehashes'
export interface PageHashDto {
hash: string,
mediaType: string,
size?: number,
}
@ -15,11 +14,12 @@ export interface PageHashMatchDto {
url: string,
pageNumber: number,
fileName: string,
fileSize: number,
mediaType: string,
}
export interface PageHashCreationDto {
hash: string,
mediaType: string,
size?: number,
action: PageHashAction,
}

View file

@ -211,7 +211,7 @@ export default Vue.extend({
},
pageHashUpdated(updated: PageHashKnownDto) {
this.elements.find(x => {
if (x.hash === updated.hash && x.mediaType === updated.mediaType && x.size === updated.size) {
if (x.hash === updated.hash) {
x.action = updated.action
return true
}

View file

@ -0,0 +1,33 @@
ALTER TABLE PAGE_HASH
RENAME TO _PAGE_HASH_OLD;
CREATE TABLE PAGE_HASH
(
HASH varchar NOT NULL PRIMARY KEY,
SIZE int8 NULL,
ACTION varchar NOT NULL,
DELETE_COUNT int NOT NULL default 0,
CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO PAGE_HASH(HASH, ACTION, SIZE, DELETE_COUNT, CREATED_DATE, LAST_MODIFIED_DATE)
SELECT HASH, ACTION, SIZE, DELETE_COUNT, CREATED_DATE, LAST_MODIFIED_DATE
FROM _PAGE_HASH_OLD;
DROP TABLE _PAGE_HASH_OLD;
ALTER TABLE PAGE_HASH_THUMBNAIL
RENAME TO _PAGE_HASH_THUMBNAIL_OLD;
CREATE TABLE PAGE_HASH_THUMBNAIL
(
HASH varchar NOT NULL PRIMARY KEY,
THUMBNAIL blob NOT NULL
);
INSERT INTO PAGE_HASH_THUMBNAIL(HASH, THUMBNAIL)
SELECT HASH, THUMBNAIL
FROM _PAGE_HASH_THUMBNAIL_OLD;
DROP TABLE _PAGE_HASH_THUMBNAIL_OLD;

View file

@ -2,7 +2,6 @@ package org.gotson.komga.domain.model
open class PageHash(
val hash: String,
val mediaType: String,
size: Long? = null,
) {
val size: Long? = if (size != null && size < 0) null else size

View file

@ -4,14 +4,13 @@ import java.time.LocalDateTime
class PageHashKnown(
hash: String,
mediaType: String,
size: Long? = null,
val action: Action,
val deleteCount: Int = 0,
override val createdDate: LocalDateTime = LocalDateTime.now(),
override val lastModifiedDate: LocalDateTime = createdDate,
) : Auditable, PageHash(hash, mediaType, size) {
) : Auditable, PageHash(hash, size) {
enum class Action {
DELETE_AUTO,
DELETE_MANUAL,
@ -20,13 +19,11 @@ class PageHashKnown(
fun copy(
hash: String = this.hash,
mediaType: String = this.mediaType,
size: Long? = this.size,
action: Action = this.action,
deleteCount: Int = this.deleteCount,
) = PageHashKnown(
hash = hash,
mediaType = mediaType,
size = size,
action = action,
deleteCount = deleteCount,

View file

@ -7,4 +7,6 @@ data class PageHashMatch(
val url: URL,
val pageNumber: Int,
val fileName: String,
val fileSize: Long,
val mediaType: String,
)

View file

@ -2,7 +2,6 @@ package org.gotson.komga.domain.model
class PageHashUnknown(
hash: String,
mediaType: String,
size: Long? = null,
val matchCount: Int = 0,
) : PageHash(hash, mediaType, size)
) : PageHash(hash, size)

View file

@ -1,7 +1,6 @@
package org.gotson.komga.domain.persistence
import org.gotson.komga.domain.model.BookPageNumbered
import org.gotson.komga.domain.model.PageHash
import org.gotson.komga.domain.model.PageHashKnown
import org.gotson.komga.domain.model.PageHashMatch
import org.gotson.komga.domain.model.PageHashUnknown
@ -9,14 +8,14 @@ import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
interface PageHashRepository {
fun findKnown(pageHash: PageHash): PageHashKnown?
fun findKnown(pageHash: String): PageHashKnown?
fun findAllKnown(actions: List<PageHashKnown.Action>?, pageable: Pageable): Page<PageHashKnown>
fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown>
fun findMatchesByHash(pageHash: PageHash, libraryId: String?, pageable: Pageable): Page<PageHashMatch>
fun findMatchesByHash(pageHash: String, pageable: Pageable): Page<PageHashMatch>
fun findMatchesByKnownHashAction(actions: List<PageHashKnown.Action>?, libraryId: String?): Map<String, Collection<BookPageNumbered>>
fun getKnownThumbnail(pageHash: PageHash): ByteArray?
fun getKnownThumbnail(pageHash: String): ByteArray?
fun insert(pageHash: PageHashKnown, thumbnail: ByteArray?)
fun update(pageHash: PageHashKnown)

View file

@ -16,7 +16,6 @@ import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
import org.gotson.komga.domain.model.MediaType
import org.gotson.komga.domain.model.MediaUnsupportedException
import org.gotson.komga.domain.model.PageHash
import org.gotson.komga.domain.model.restoreHashFrom
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.HistoricalEventRepository
@ -157,7 +156,7 @@ class BookPageEditor(
bookRepository.update(newBook)
mediaRepository.update(mediaWithHashes)
pagesToDelete
.mapNotNull { pageHashRepository.findKnown(PageHash(it.fileHash, it.mediaType, it.fileSize)) }
.mapNotNull { pageHashRepository.findKnown(it.fileHash) }
.forEach { pageHashRepository.update(it.copy(deleteCount = it.deleteCount + 1)) }
}

View file

@ -5,7 +5,6 @@ import org.gotson.komga.domain.model.BookPageContent
import org.gotson.komga.domain.model.BookPageNumbered
import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.model.MediaType
import org.gotson.komga.domain.model.PageHash
import org.gotson.komga.domain.model.PageHashKnown
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.MediaRepository
@ -39,8 +38,8 @@ class PageHashLifecycle(
emptyList()
}
fun getPage(pageHash: PageHash, resizeTo: Int? = null): BookPageContent? {
val match = pageHashRepository.findMatchesByHash(pageHash, null, Pageable.ofSize(1)).firstOrNull() ?: return null
fun getPage(pageHash: String, resizeTo: Int? = null): BookPageContent? {
val match = pageHashRepository.findMatchesByHash(pageHash, Pageable.ofSize(1)).firstOrNull() ?: return null
val book = bookRepository.findByIdOrNull(match.bookId) ?: return null
return bookLifecycle.getBookPage(book, match.pageNumber, resizeTo = resizeTo)
@ -50,17 +49,11 @@ class PageHashLifecycle(
pageHashRepository.findMatchesByKnownHashAction(listOf(PageHashKnown.Action.DELETE_AUTO), library.id)
fun createOrUpdate(pageHash: PageHashKnown) {
if (pageHash.action == PageHashKnown.Action.DELETE_AUTO && pageHash.size == null) throw IllegalArgumentException("cannot create PageHash without size and Action.DELETE_AUTO")
val existing = pageHashRepository.findKnown(pageHash)
val existing = pageHashRepository.findKnown(pageHash.hash)
if (existing == null) {
pageHashRepository.insert(pageHash, getPage(pageHash, 500)?.content)
pageHashRepository.insert(pageHash, getPage(pageHash.hash, 500)?.content)
} else {
pageHashRepository.update(existing.copy(action = pageHash.action))
}
}
fun update(pageHash: PageHashKnown) {
if (pageHash.action == PageHashKnown.Action.DELETE_AUTO && pageHash.size == null) throw IllegalArgumentException("cannot create PageHash without size and Action.DELETE_AUTO")
}
}

View file

@ -1,7 +1,6 @@
package org.gotson.komga.infrastructure.jooq
import org.gotson.komga.domain.model.BookPageNumbered
import org.gotson.komga.domain.model.PageHash
import org.gotson.komga.domain.model.PageHashKnown
import org.gotson.komga.domain.model.PageHashMatch
import org.gotson.komga.domain.model.PageHashUnknown
@ -41,7 +40,6 @@ class PageHashDao(
private val sortsUnknown = mapOf(
"hash" to p.FILE_HASH,
"mediatype" to p.MEDIA_TYPE,
"fileSize" to p.FILE_SIZE,
"matchCount" to DSL.field("count"),
"totalSize" to DSL.field("totalSize"),
@ -50,14 +48,9 @@ class PageHashDao(
"pageNumber" to p.NUMBER,
)
override fun findKnown(pageHash: PageHash): PageHashKnown? =
override fun findKnown(pageHash: String): PageHashKnown? =
dsl.selectFrom(ph)
.where(ph.HASH.eq(pageHash.hash))
.and(ph.MEDIA_TYPE.eq(pageHash.mediaType))
.apply {
if (pageHash.size == null) and(ph.SIZE.isNull)
else and(ph.SIZE.eq(pageHash.size))
}
.where(ph.HASH.eq(pageHash))
.fetchOneInto(ph)
?.toDomain()
@ -87,7 +80,6 @@ class PageHashDao(
val bookCount = DSL.count(p.BOOK_ID)
val query = dsl.select(
p.FILE_HASH,
p.MEDIA_TYPE,
p.FILE_SIZE,
bookCount.`as`("count"),
(bookCount * p.FILE_SIZE).`as`("totalSize"),
@ -98,16 +90,10 @@ class PageHashDao(
DSL.notExists(
dsl.selectOne()
.from(ph)
.where(ph.HASH.eq(p.FILE_HASH))
.and(ph.MEDIA_TYPE.eq(p.MEDIA_TYPE))
.and(
ph.SIZE.eq(p.FILE_SIZE).or(
ph.SIZE.isNull.and(p.FILE_SIZE.isNull),
),
),
.where(ph.HASH.eq(p.FILE_HASH)),
),
)
.groupBy(p.FILE_HASH, p.MEDIA_TYPE, p.FILE_SIZE)
.groupBy(p.FILE_HASH)
.having(DSL.count(p.BOOK_ID).gt(1))
val count = dsl.fetchCount(query)
@ -117,7 +103,7 @@ class PageHashDao(
.orderBy(orderBy)
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
.fetch {
PageHashUnknown(it.value1(), it.value2(), it.value3(), it.value4())
PageHashUnknown(it.value1(), it.value2(), it.value3())
}
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
@ -129,17 +115,11 @@ class PageHashDao(
)
}
override fun findMatchesByHash(pageHash: PageHash, libraryId: String?, pageable: Pageable): Page<PageHashMatch> {
val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER, p.FILE_NAME)
override fun findMatchesByHash(pageHash: String, pageable: Pageable): Page<PageHashMatch> {
val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER, p.FILE_NAME, p.FILE_SIZE, p.MEDIA_TYPE)
.from(p)
.leftJoin(b).on(p.BOOK_ID.eq(b.ID))
.where(p.FILE_HASH.eq(pageHash.hash))
.and(p.MEDIA_TYPE.eq(pageHash.mediaType))
.apply {
if (pageHash.size == null) and(p.FILE_SIZE.isNull)
else and(p.FILE_SIZE.eq(pageHash.size))
}
.apply { libraryId?.let { and(b.LIBRARY_ID.eq(it)) } }
.where(p.FILE_HASH.eq(pageHash))
val count = dsl.fetchCount(query)
@ -153,6 +133,8 @@ class PageHashDao(
url = URL(it.value2()),
pageNumber = it.value3() + 1,
fileName = it.value4(),
fileSize = it.value5(),
mediaType = it.value6(),
)
}
@ -168,13 +150,7 @@ class PageHashDao(
override fun findMatchesByKnownHashAction(actions: List<PageHashKnown.Action>?, libraryId: String?): Map<String, Collection<BookPageNumbered>> =
dsl.select(p.BOOK_ID, p.FILE_NAME, p.NUMBER, p.FILE_HASH, p.MEDIA_TYPE, p.FILE_SIZE)
.from(p)
.innerJoin(ph).on(
p.FILE_HASH.eq(ph.HASH)
.and(p.MEDIA_TYPE.eq(ph.MEDIA_TYPE))
.and(
p.FILE_SIZE.isNull.and(ph.SIZE.isNull).or(p.FILE_SIZE.isNotNull.and(ph.SIZE.isNotNull).and(p.FILE_SIZE.eq(ph.SIZE))),
),
)
.innerJoin(ph).on(p.FILE_HASH.eq(ph.HASH))
.apply { libraryId?.let<String, Unit> { innerJoin(b).on(b.ID.eq(p.BOOK_ID)) } }
.where(ph.ACTION.`in`(actions))
.apply { libraryId?.let<String, Unit> { and(b.LIBRARY_ID.eq(it)) } }
@ -189,22 +165,16 @@ class PageHashDao(
}.groupingBy { it.first }
.fold(emptyList()) { acc, (_, new) -> acc + new }
override fun getKnownThumbnail(pageHash: PageHash): ByteArray? =
override fun getKnownThumbnail(pageHash: String): ByteArray? =
dsl.select(pht.THUMBNAIL)
.from(pht)
.where(pht.HASH.eq(pageHash.hash))
.and(pht.MEDIA_TYPE.eq(pageHash.mediaType))
.apply {
if (pageHash.size == null) and(pht.SIZE.isNull)
else and(pht.SIZE.eq(pageHash.size))
}
.where(pht.HASH.eq(pageHash))
.fetchOne()?.value1()
@Transactional
override fun insert(pageHash: PageHashKnown, thumbnail: ByteArray?) {
dsl.insertInto(ph)
.set(ph.HASH, pageHash.hash)
.set(ph.MEDIA_TYPE, pageHash.mediaType)
.set(ph.SIZE, pageHash.size)
.set(ph.ACTION, pageHash.action.name)
.execute()
@ -212,8 +182,6 @@ class PageHashDao(
if (thumbnail != null) {
dsl.insertInto(pht)
.set(pht.HASH, pageHash.hash)
.set(pht.MEDIA_TYPE, pageHash.mediaType)
.set(pht.SIZE, pageHash.size)
.set(pht.THUMBNAIL, thumbnail)
.execute()
}
@ -222,14 +190,10 @@ class PageHashDao(
override fun update(pageHash: PageHashKnown) {
dsl.update(ph)
.set(ph.ACTION, pageHash.action.name)
.set(ph.SIZE, pageHash.size)
.set(ph.DELETE_COUNT, pageHash.deleteCount)
.set(ph.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
.where(ph.HASH.eq(pageHash.hash))
.and(ph.MEDIA_TYPE.eq(pageHash.mediaType))
.apply {
if (pageHash.size == null) and(ph.SIZE.isNull)
else and(ph.SIZE.eq(pageHash.size))
}
.execute()
}
}
@ -237,7 +201,6 @@ class PageHashDao(
private fun PageHashRecord.toDomain() =
PageHashKnown(
hash = hash,
mediaType = mediaType,
size = size,
deleteCount = deleteCount,
action = PageHashKnown.Action.valueOf(action),

View file

@ -7,7 +7,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import jakarta.validation.Valid
import org.gotson.komga.application.tasks.TaskEmitter
import org.gotson.komga.domain.model.BookPageNumbered
import org.gotson.komga.domain.model.PageHash
import org.gotson.komga.domain.model.PageHashKnown
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.persistence.PageHashRepository
@ -57,10 +56,8 @@ class PageHashController(
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
fun getKnownPageHashThumbnail(
@PathVariable pageHash: String,
@RequestParam("media_type") mediaType: String,
@RequestParam("file_size") size: Long,
): ByteArray =
pageHashRepository.getKnownThumbnail(PageHash(pageHash, mediaType, size))
pageHashRepository.getKnownThumbnail(pageHash)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@GetMapping("/unknown")
@ -74,13 +71,10 @@ class PageHashController(
@PageableAsQueryParam
fun getPageHashMatches(
@PathVariable pageHash: String,
@RequestParam("media_type") mediaType: String,
@RequestParam("file_size") size: Long,
@Parameter(hidden = true) page: Pageable,
): Page<PageHashMatchDto> =
pageHashRepository.findMatchesByHash(
PageHash(pageHash, mediaType, size),
null,
pageHash,
page,
).map { it.toDto() }
@ -88,14 +82,9 @@ class PageHashController(
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
fun getUnknownPageHashThumbnail(
@PathVariable pageHash: String,
@RequestParam("media_type") mediaType: String,
@RequestParam("file_size") size: Long,
@RequestParam("resize") resize: Int? = null,
): ResponseEntity<ByteArray> =
pageHashLifecycle.getPage(
PageHash(pageHash, mediaType, size),
resize,
)?.let {
pageHashLifecycle.getPage(pageHash, resize)?.let {
ResponseEntity.ok()
.contentType(getMediaTypeOrDefault(it.mediaType))
.body(it.content)
@ -111,7 +100,6 @@ class PageHashController(
pageHashLifecycle.createOrUpdate(
PageHashKnown(
hash = pageHash.hash,
mediaType = pageHash.mediaType,
size = pageHash.size,
action = pageHash.action,
),
@ -125,20 +113,16 @@ class PageHashController(
@ResponseStatus(HttpStatus.ACCEPTED)
fun performDelete(
@PathVariable pageHash: String,
@RequestParam("media_type") mediaType: String,
@RequestParam("file_size") size: Long,
) {
val hash = PageHash(pageHash, mediaType, size)
val toRemove = pageHashRepository.findMatchesByHash(hash, null, Pageable.unpaged())
val toRemove = pageHashRepository.findMatchesByHash(pageHash, Pageable.unpaged())
.groupBy(
{ it.bookId },
{
BookPageNumbered(
fileName = it.fileName,
mediaType = hash.mediaType,
fileHash = hash.hash,
fileSize = hash.size,
mediaType = it.mediaType,
fileHash = pageHash,
fileSize = it.fileSize,
pageNumber = it.pageNumber,
)
},
@ -151,8 +135,6 @@ class PageHashController(
@ResponseStatus(HttpStatus.ACCEPTED)
fun deleteSingleMatch(
@PathVariable pageHash: String,
@RequestParam("media_type") mediaType: String,
@RequestParam("file_size") size: Long,
@RequestBody matchDto: PageHashMatchDto,
) {
val toRemove = Pair(
@ -160,9 +142,9 @@ class PageHashController(
listOf(
BookPageNumbered(
fileName = matchDto.fileName,
mediaType = mediaType,
mediaType = matchDto.mediaType,
fileHash = pageHash,
fileSize = size,
fileSize = matchDto.fileSize,
pageNumber = matchDto.pageNumber,
),
),

View file

@ -5,7 +5,6 @@ import org.gotson.komga.domain.model.PageHashKnown
data class PageHashCreationDto(
@get:NotBlank val hash: String,
@get:NotBlank val mediaType: String,
val size: Long? = null,
val action: PageHashKnown.Action,
)

View file

@ -6,7 +6,6 @@ import java.time.LocalDateTime
data class PageHashKnownDto(
val hash: String,
val mediaType: String,
val size: Long?,
val action: PageHashKnown.Action,
val deleteCount: Int,
@ -17,7 +16,6 @@ data class PageHashKnownDto(
fun PageHashKnown.toDto() = PageHashKnownDto(
hash = hash,
mediaType = mediaType,
size = size,
action = action,
deleteCount = deleteCount,

View file

@ -8,6 +8,8 @@ data class PageHashMatchDto(
val url: String,
val pageNumber: Int,
val fileName: String,
val fileSize: Long,
val mediaType: String,
)
fun PageHashMatch.toDto() =
@ -16,4 +18,6 @@ fun PageHashMatch.toDto() =
url = url.toFilePath(),
pageNumber = pageNumber,
fileName = fileName,
fileSize = fileSize,
mediaType = mediaType,
)

View file

@ -4,14 +4,12 @@ import org.gotson.komga.domain.model.PageHashUnknown
data class PageHashUnknownDto(
val hash: String,
val mediaType: String,
val size: Long?,
val matchCount: Int,
)
fun PageHashUnknown.toDto() = PageHashUnknownDto(
hash = hash,
mediaType = mediaType,
size = size,
matchCount = matchCount,
)

View file

@ -7,21 +7,21 @@ class PageHashTest {
@Test
fun `given negative size when creating a PageHash then its size is null`() {
val pageHash = PageHash("abc", "image/jpeg", -5)
val pageHash = PageHash("abc", -5)
assertThat(pageHash.size).isNull()
}
@Test
fun `given null size when creating a PageHash then its size is null`() {
val pageHash = PageHash("abc", "image/jpeg", null)
val pageHash = PageHash("abc", null)
assertThat(pageHash.size).isNull()
}
@Test
fun `given size when creating a PageHash then its size is not null`() {
val pageHash = PageHash("abc", "image/jpeg", 5)
val pageHash = PageHash("abc", 5)
assertThat(pageHash.size).isNotNull
}

View file

@ -1,34 +0,0 @@
package org.gotson.komga.domain.service
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
import org.gotson.komga.domain.model.PageHashKnown
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.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringExtension::class)
@SpringBootTest
class PageHashLifecycleTest(
@Autowired private val pageHashLifecycle: PageHashLifecycle,
) {
@Test
fun `given a page hash without size and action DELETE_AUTO when creating it then IllegalArgumentException is thrown`() {
// given
val pageHash = PageHashKnown(
hash = "abcdef",
mediaType = "image/jpeg",
size = null,
action = PageHashKnown.Action.DELETE_AUTO,
)
// when
val thrown = catchThrowable { pageHashLifecycle.createOrUpdate(pageHash) }
// then
assertThat(thrown).isExactlyInstanceOf(IllegalArgumentException::class.java)
}
}

View file

@ -66,16 +66,14 @@ class PageHashDaoTest(
val now = LocalDateTime.now()
val pageHash = PageHashKnown(
hash = "hashed",
mediaType = "image/jpeg",
size = 10,
action = PageHashKnown.Action.IGNORE,
)
pageHashDao.insert(pageHash, null)
val known = pageHashDao.findKnown(pageHash)!!
val known = pageHashDao.findKnown(pageHash.hash)!!
assertThat(known.hash).isEqualTo(pageHash.hash)
assertThat(known.mediaType).isEqualTo(pageHash.mediaType)
assertThat(known.size).isEqualTo(pageHash.size)
assertThat(known.action).isEqualTo(pageHash.action)
assertThat(known.createdDate).isCloseTo(now, offset)
@ -91,7 +89,6 @@ class PageHashDaoTest(
pageHashDao.insert(
PageHashKnown(
hash = "hash-1",
mediaType = "image/jpeg",
size = 1,
action = PageHashKnown.Action.IGNORE,
),
@ -101,7 +98,6 @@ class PageHashDaoTest(
pageHashDao.insert(
PageHashKnown(
hash = "hash-2",
mediaType = "image/jpeg",
size = null,
action = PageHashKnown.Action.IGNORE,
),
@ -130,10 +126,11 @@ class PageHashDaoTest(
val unknown = pageHashDao.findAllUnknown(Pageable.unpaged())
// then
assertThat(unknown).hasSize(9)
assertThat(unknown).hasSize(8)
assertThat(unknown.map { it.hash })
.doesNotContain("hash-1")
.containsExactlyInAnyOrderElementsOf((2..10).map { "hash-$it" })
.doesNotContain("hash-2")
.containsExactlyInAnyOrderElementsOf((3..10).map { "hash-$it" })
}
}
}