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:
@@ -0,0 +1,14 @@
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
enum class CbzMediaType(
|
||||
val mediaType: String,
|
||||
) {
|
||||
MODERN("application/vnd.comicbook+zip"),
|
||||
LEGACY("application/x-cbz"),
|
||||
COMPATIBLE("application/x-cbr"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(channel: String): CbzMediaType = entries.find { it.name.lowercase() == channel.lowercase() } ?: MODERN
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
import suwayomi.tachidesk.graphql.types.AuthMode
|
||||
import suwayomi.tachidesk.graphql.types.CbzMediaType
|
||||
import suwayomi.tachidesk.graphql.types.DatabaseType
|
||||
import suwayomi.tachidesk.graphql.types.DownloadConversion
|
||||
import suwayomi.tachidesk.graphql.types.KoreaderSyncChecksumMethod
|
||||
@@ -836,6 +837,17 @@ class ServerConfig(
|
||||
defaultValue = BackupFlags.DEFAULT.includeServerSettings,
|
||||
)
|
||||
|
||||
val opdsCbzMimetype: MutableStateFlow<CbzMediaType> by EnumSetting(
|
||||
protoNumber = 83,
|
||||
group = SettingGroup.OPDS,
|
||||
defaultValue = CbzMediaType.MODERN,
|
||||
enumClass = CbzMediaType::class,
|
||||
typeInfo = SettingsRegistry.PartialTypeInfo(imports = listOf("suwayomi.tachidesk.graphql.types.CbzMediaType")),
|
||||
excludeFromBackup = true,
|
||||
description = "Controls the MimeType that Suwayomi sends in OPDS entries for CBZ archives. Also affects global CBZ download. Modern follows recent IANA standard (2017), while LEGACY (deprecated mimetype for .cbz) and COMPATIBLE (deprecated mimetype for all comic archives) might be more compatible with older clients.",
|
||||
)
|
||||
|
||||
|
||||
|
||||
/** ****************************************************************** **/
|
||||
/** **/
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user