mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 04:22:28 +02:00
fix(webui): duplicate page matches were not showing exact matches only
This commit is contained in:
parent
7a3f80ce92
commit
5844521286
9 changed files with 65 additions and 33 deletions
|
|
@ -31,15 +31,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue'
|
import Vue, {PropType} from 'vue'
|
||||||
import {bookPageThumbnailUrl} from '@/functions/urls'
|
import {bookPageThumbnailUrl} from '@/functions/urls'
|
||||||
import {PageHashMatchDto} from '@/types/komga-pagehashes'
|
import {PageHashMatchDto, PageHashUnknownDto} from '@/types/komga-pagehashes'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'PageHashMatchesTable',
|
name: 'PageHashMatchesTable',
|
||||||
props: {
|
props: {
|
||||||
hash: {
|
hash: {
|
||||||
type: String,
|
type: Object as PropType<PageHashUnknownDto>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -73,7 +73,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadData(hash: string) {
|
async loadData(hash: PageHashUnknownDto) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
const {sortBy, sortDesc, page, itemsPerPage} = this.options
|
const {sortBy, sortDesc, page, itemsPerPage} = this.options
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {PageHashUnknownDto} from '@/types/komga-pagehashes'
|
||||||
|
|
||||||
const fullUrl = process.env.VUE_APP_KOMGA_API_URL
|
const fullUrl = process.env.VUE_APP_KOMGA_API_URL
|
||||||
? process.env.VUE_APP_KOMGA_API_URL
|
? process.env.VUE_APP_KOMGA_API_URL
|
||||||
: window.location.origin + window.resourceBaseUrl
|
: window.location.origin + window.resourceBaseUrl
|
||||||
|
|
@ -72,10 +74,10 @@ export function transientBookPageUrl(transientBookId: string, page: number): str
|
||||||
return `${urls.originNoSlash}/api/v1/transient-books/${transientBookId}/pages/${page}`
|
return `${urls.originNoSlash}/api/v1/transient-books/${transientBookId}/pages/${page}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pageHashUnknownThumbnailUrl(hash: string, resize?: number): string {
|
export function pageHashUnknownThumbnailUrl(pageHash: PageHashUnknownDto, resize?: number): string {
|
||||||
let url = `${urls.originNoSlash}/api/v1/page-hashes/unknown/${hash}/thumbnail`
|
let url = `${urls.originNoSlash}/api/v1/page-hashes/unknown/${pageHash.hash}/thumbnail?media_type=${pageHash.mediaType}&size=${pageHash.sizeBytes || -1}`
|
||||||
if(resize) {
|
if(resize) {
|
||||||
url += `?resize=${resize}`
|
url += `&resize=${resize}`
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,15 @@ export default class KomgaPageHashesService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUnknownPageHashMatches(hash: string, pageRequest?: PageRequest): Promise<Page<PageHashMatchDto>> {
|
async getUnknownPageHashMatches(hash: PageHashUnknownDto, pageRequest?: PageRequest): Promise<Page<PageHashMatchDto>> {
|
||||||
try {
|
try {
|
||||||
return (await this.http.get(`${API_PAGE_HASH}/unknown/${hash}`, {
|
const params = {
|
||||||
params: pageRequest,
|
...pageRequest,
|
||||||
|
media_type: hash.mediaType,
|
||||||
|
size: hash.sizeBytes || -1,
|
||||||
|
}
|
||||||
|
return (await this.http.get(`${API_PAGE_HASH}/unknown/${hash.hash}`, {
|
||||||
|
params: params,
|
||||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||||
})).data
|
})).data
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
width="200"
|
width="200"
|
||||||
height="300"
|
height="300"
|
||||||
contain
|
contain
|
||||||
@click="showDialogImage(element.hash)"
|
@click="showDialogImage(element)"
|
||||||
:src="pageHashUnknownThumbnailUrl(element.hash, 500)"
|
:src="pageHashUnknownThumbnailUrl(element, 500)"
|
||||||
style="cursor: zoom-in"
|
style="cursor: zoom-in"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
<div>{{ element.mediaType }}</div>
|
<div>{{ element.mediaType }}</div>
|
||||||
<div>{{ element.size || $t('duplicate_pages.unknown_size') }}</div>
|
<div>{{ element.size || $t('duplicate_pages.unknown_size') }}</div>
|
||||||
<v-btn
|
<v-btn
|
||||||
@click="showDialogMatches(element.hash)"
|
@click="showDialogMatches(element)"
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
<v-img
|
<v-img
|
||||||
@click="dialogImage = false"
|
@click="dialogImage = false"
|
||||||
contain
|
contain
|
||||||
:src="pageHashUnknownThumbnailUrl(dialogImageHash)"
|
:src="pageHashUnknownThumbnailUrl(dialogImagePageHash)"
|
||||||
style="cursor: zoom-out;"
|
style="cursor: zoom-out;"
|
||||||
/>
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<page-hash-matches-table
|
<page-hash-matches-table
|
||||||
:hash="dialogMatchesHash"
|
:hash="dialogMatchesPageHash"
|
||||||
class="my-2"
|
class="my-2"
|
||||||
/>
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
@ -102,8 +102,8 @@ export default Vue.extend({
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
dialogImage: false,
|
dialogImage: false,
|
||||||
dialogMatches: false,
|
dialogMatches: false,
|
||||||
dialogImageHash: '',
|
dialogImagePageHash: {} as PageHashUnknownDto,
|
||||||
dialogMatchesHash: '',
|
dialogMatchesPageHash: {} as PageHashUnknownDto,
|
||||||
pageHashUnknownThumbnailUrl,
|
pageHashUnknownThumbnailUrl,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -142,12 +142,12 @@ export default Vue.extend({
|
||||||
this.totalPages = itemsPage.totalPages
|
this.totalPages = itemsPage.totalPages
|
||||||
this.elements = itemsPage.content
|
this.elements = itemsPage.content
|
||||||
},
|
},
|
||||||
showDialogImage(hash: string) {
|
showDialogImage(pageHash: PageHashUnknownDto) {
|
||||||
this.dialogImageHash = hash
|
this.dialogImagePageHash = pageHash
|
||||||
this.dialogImage = true
|
this.dialogImage = true
|
||||||
},
|
},
|
||||||
showDialogMatches(hash: string) {
|
showDialogMatches(pageHash: PageHashUnknownDto) {
|
||||||
this.dialogMatchesHash = hash
|
this.dialogMatchesPageHash = pageHash
|
||||||
this.dialogMatches = true
|
this.dialogMatches = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@ data class PageHashUnknown(
|
||||||
val hash: String,
|
val hash: String,
|
||||||
val mediaType: String,
|
val mediaType: String,
|
||||||
val size: Long? = null,
|
val size: Long? = null,
|
||||||
val matchCount: Int,
|
val matchCount: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ interface PageHashRepository {
|
||||||
fun findAllKnown(actions: List<PageHash.Action>?, pageable: Pageable): Page<PageHash>
|
fun findAllKnown(actions: List<PageHash.Action>?, pageable: Pageable): Page<PageHash>
|
||||||
fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown>
|
fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown>
|
||||||
|
|
||||||
fun findMatchesByHash(hash: String, pageable: Pageable): Page<PageHashMatch>
|
fun findMatchesByHash(pageHash: PageHashUnknown, pageable: Pageable): Page<PageHashMatch>
|
||||||
|
|
||||||
fun getKnownThumbnail(hash: String): ByteArray?
|
fun getKnownThumbnail(hash: String): ByteArray?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.domain.service
|
package org.gotson.komga.domain.service
|
||||||
|
|
||||||
import org.gotson.komga.domain.model.BookPageContent
|
import org.gotson.komga.domain.model.BookPageContent
|
||||||
|
import org.gotson.komga.domain.model.PageHashUnknown
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
|
|
@ -13,7 +14,7 @@ class PageHashLifecycle(
|
||||||
private val bookRepository: BookRepository,
|
private val bookRepository: BookRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getPage(hash: String, resizeTo: Int? = null): BookPageContent? {
|
fun getPage(hash: PageHashUnknown, resizeTo: Int? = null): BookPageContent? {
|
||||||
val match = pageHashRepository.findMatchesByHash(hash, Pageable.ofSize(1)).firstOrNull() ?: return null
|
val match = pageHashRepository.findMatchesByHash(hash, Pageable.ofSize(1)).firstOrNull() ?: return null
|
||||||
val book = bookRepository.findByIdOrNull(match.bookId) ?: return null
|
val book = bookRepository.findByIdOrNull(match.bookId) ?: return null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,16 @@ class PageHashDao(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findMatchesByHash(hash: String, pageable: Pageable): Page<PageHashMatch> {
|
override fun findMatchesByHash(pageHash: PageHashUnknown, pageable: Pageable): Page<PageHashMatch> {
|
||||||
val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER)
|
val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER)
|
||||||
.from(p)
|
.from(p)
|
||||||
.leftJoin(b).on(p.BOOK_ID.eq(b.ID))
|
.leftJoin(b).on(p.BOOK_ID.eq(b.ID))
|
||||||
.where(p.FILE_HASH.eq(hash))
|
.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))
|
||||||
|
}
|
||||||
|
|
||||||
val count = dsl.fetchCount(query)
|
val count = dsl.fetchCount(query)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import org.gotson.komga.domain.model.PageHash
|
import org.gotson.komga.domain.model.PageHash
|
||||||
|
import org.gotson.komga.domain.model.PageHashUnknown
|
||||||
import org.gotson.komga.domain.model.ROLE_ADMIN
|
import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||||
import org.gotson.komga.domain.service.PageHashLifecycle
|
import org.gotson.komga.domain.service.PageHashLifecycle
|
||||||
|
|
@ -45,7 +46,7 @@ class PageHashController(
|
||||||
): Page<PageHashDto> =
|
): Page<PageHashDto> =
|
||||||
pageHashRepository.findAllKnown(actions, page).map { it.toDto() }
|
pageHashRepository.findAllKnown(actions, page).map { it.toDto() }
|
||||||
|
|
||||||
@GetMapping("/{hash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE],)
|
@GetMapping("/{hash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||||
fun getPageHashThumbnail(@PathVariable hash: String): ByteArray =
|
fun getPageHashThumbnail(@PathVariable hash: String): ByteArray =
|
||||||
pageHashRepository.getKnownThumbnail(hash) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
pageHashRepository.getKnownThumbnail(hash) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
@ -57,21 +58,39 @@ class PageHashController(
|
||||||
): Page<PageHashUnknownDto> =
|
): Page<PageHashUnknownDto> =
|
||||||
pageHashRepository.findAllUnknown(page).map { it.toDto() }
|
pageHashRepository.findAllUnknown(page).map { it.toDto() }
|
||||||
|
|
||||||
@GetMapping("unknown/{hash}")
|
@GetMapping("unknown/{pageHash}")
|
||||||
@PageableAsQueryParam
|
@PageableAsQueryParam
|
||||||
fun getUnknownPageHashMatches(
|
fun getUnknownPageHashMatches(
|
||||||
@PathVariable hash: String,
|
@PathVariable pageHash: String,
|
||||||
|
@RequestParam("media_type") mediaType: String,
|
||||||
|
@RequestParam("size") size: Long,
|
||||||
@Parameter(hidden = true) page: Pageable,
|
@Parameter(hidden = true) page: Pageable,
|
||||||
): Page<PageHashMatchDto> =
|
): Page<PageHashMatchDto> =
|
||||||
pageHashRepository.findMatchesByHash(hash, page).map { it.toDto() }
|
pageHashRepository.findMatchesByHash(
|
||||||
|
PageHashUnknown(
|
||||||
|
hash = pageHash,
|
||||||
|
mediaType = mediaType,
|
||||||
|
size = if (size < 0) null else size,
|
||||||
|
),
|
||||||
|
page,
|
||||||
|
).map { it.toDto() }
|
||||||
|
|
||||||
@GetMapping("unknown/{hash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE],)
|
@GetMapping("unknown/{pageHash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||||
fun getUnknownPageHashThumbnail(
|
fun getUnknownPageHashThumbnail(
|
||||||
@PathVariable hash: String,
|
@PathVariable pageHash: String,
|
||||||
|
@RequestParam("media_type") mediaType: String,
|
||||||
|
@RequestParam("size") size: Long,
|
||||||
@RequestParam("resize") resize: Int? = null,
|
@RequestParam("resize") resize: Int? = null,
|
||||||
): ResponseEntity<ByteArray> =
|
): ResponseEntity<ByteArray> =
|
||||||
pageHashLifecycle.getPage(hash, resize)?.let {
|
pageHashLifecycle.getPage(
|
||||||
|
PageHashUnknown(
|
||||||
|
hash = pageHash,
|
||||||
|
mediaType = mediaType,
|
||||||
|
size = if (size < 0) null else size,
|
||||||
|
),
|
||||||
|
resize,
|
||||||
|
)?.let {
|
||||||
ResponseEntity.ok()
|
ResponseEntity.ok()
|
||||||
.contentType(getMediaTypeOrDefault(it.mediaType))
|
.contentType(getMediaTypeOrDefault(it.mediaType))
|
||||||
.body(it.content)
|
.body(it.content)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue