mirror of
https://github.com/gotson/komga.git
synced 2026-04-30 10:52:59 +02:00
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:
parent
a03bda9797
commit
2d95679dd4
23 changed files with 88 additions and 182 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@ data class PageHashMatch(
|
|||
val url: URL,
|
||||
val pageNumber: Int,
|
||||
val fileName: String,
|
||||
val fileSize: Long,
|
||||
val mediaType: String,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue