OPDS: Offer CBZ in older mimetype (#1731)

* OPDS: Offer CBZ in older mimetype

* OPDS: Include length when offering CBZ download

* Disable compression on CBZ endpoint

Zipping a zip

* CBZ download match content type of OPDS

* Move compression disable

* Introduce setting for configuring CBZ mimetype

* Document new option

[no-ci]

* Update server/src/main/kotlin/suwayomi/tachidesk/opds/impl/OpdsEntryBuilder.kt

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
This commit is contained in:
Constantin Piber
2025-10-25 00:36:59 +02:00
committed by GitHub
parent 68492bf591
commit bc6e28cabe
8 changed files with 47 additions and 16 deletions
@@ -29,6 +29,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.user.requireUser
import suwayomi.tachidesk.server.util.formParam
import suwayomi.tachidesk.server.util.handler
@@ -507,10 +508,12 @@ object MangaController {
},
behaviorOf = { ctx, chapterId, markAsRead ->
ctx.getAttribute(Attribute.TachideskUser).requireUser()
ctx.disableCompression()
val contentType = serverConfig.opdsCbzMimetype.value.mediaType
if (ctx.method() == HandlerType.HEAD) {
ctx.future {
future { ChapterDownloadHelper.getCbzMetadataForDownload(chapterId) }
.thenApply { (fileName, fileSize, contentType) ->
.thenApply { (fileName, fileSize) ->
ctx.header("Content-Type", contentType)
ctx.header("Content-Disposition", "attachment; filename=\"$fileName\"")
ctx.header("Content-Length", fileSize.toString())
@@ -522,7 +525,7 @@ object MangaController {
ctx.future {
future { ChapterDownloadHelper.getCbzForDownload(chapterId, shouldMarkAsRead) }
.thenApply { (inputStream, fileName, fileSize) ->
ctx.header("Content-Type", "application/vnd.comicbook+zip")
ctx.header("Content-Type", contentType)
ctx.header("Content-Disposition", "attachment; filename=\"$fileName\"")
ctx.header("Content-Length", fileSize.toString())
ctx.result(inputStream)
@@ -100,12 +100,11 @@ object ChapterDownloadHelper {
return Triple(cbzFile.first, fileName, cbzFile.second)
}
fun getCbzMetadataForDownload(chapterId: Int): Triple<String, Long, String> { // fileName, fileSize, contentType
fun getCbzMetadataForDownload(chapterId: Int): Pair<String, Long> { // fileName, fileSize
val (chapterData, fileName) = getChapterWithCbzFileName(chapterId)
val fileSize = provider(chapterData.mangaId, chapterData.id).getArchiveSize()
val contentType = "application/vnd.comicbook+zip"
return Triple(fileName, fileSize, contentType)
return Pair(fileName, fileSize)
}
}
@@ -40,5 +40,4 @@ object OpdsConstants {
const val TYPE_ATOM_XML_ENTRY_PROFILE_OPDS = "application/atom+xml;type=entry;profile=opds-catalog"
const val TYPE_OPENSEARCH_DESCRIPTION = "application/opensearchdescription+xml"
const val TYPE_IMAGE_JPEG = "image/jpeg"
const val TYPE_CBZ = "application/vnd.comicbook+zip"
}
@@ -336,6 +336,14 @@ object OpdsEntryBuilder {
}
val entryTitle = "$titlePrefix ${chapter.name}"
val cbzFileSize =
if (chapter.downloaded) {
withContext(Dispatchers.IO) {
runCatching { ChapterDownloadHelper.getArchiveStreamWithSize(manga.id, chapter.id).second }.getOrNull()
}
} else {
null
}
val links = mutableListOf<OpdsLinkXml>()
chapter.url?.let {
@@ -348,8 +356,9 @@ object OpdsEntryBuilder {
OpdsLinkXml(
OpdsConstants.LINK_REL_ACQUISITION_OPEN_ACCESS,
"/api/v1/chapter/${chapter.id}/download?markAsRead=${serverConfig.opdsMarkAsReadOnDownload.value}",
OpdsConstants.TYPE_CBZ,
serverConfig.opdsCbzMimetype.value.mediaType,
MR.strings.opds_linktitle_download_cbz.localized(locale),
length = cbzFileSize,
),
)
}
@@ -411,15 +420,6 @@ object OpdsEntryBuilder {
)
}
val cbzFileSize =
if (chapter.downloaded) {
withContext(Dispatchers.IO) {
runCatching { ChapterDownloadHelper.getArchiveStreamWithSize(manga.id, chapter.id).second }.getOrNull()
}
} else {
null
}
return OpdsEntryXml(
id = "urn:suwayomi:chapter:${chapter.id}:metadata$idSuffix",
title = entryTitle,
@@ -19,6 +19,8 @@ data class OpdsLinkXml(
// Thread count
@XmlSerialName("count", OpdsConstants.NS_THREAD, "thr")
val thrCount: Int? = null,
// link download size in bytes
val length: Long? = null,
// OPDS-PSE attributes
@XmlSerialName("count", OpdsConstants.NS_PSE, "pse")
val pseCount: Int? = null,