mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-10 14:24:34 -05:00
fix(api): optimize HEAD requests for chapter downloads (#1601)
Previously, handling a HEAD request on the chapter download endpoint was inefficient as it triggered the full CBZ file generation process in-memory just to retrieve metadata like Content-Length and Content-Disposition. This caused unnecessary latency especially for OPDS clients. This commit introduces a separate, lightweight path for HEAD requests. - A new `getCbzMetadataForDownload` method is added to `ChapterDownloadHelper` to calculate the filename and file size without generating an archive stream. - The `ChaptersFilesProvider` interface is updated with a `getArchiveSize()` method, implemented by both `ArchiveProvider` and `FolderProvider`, to retrieve the total size. - The `MangaController` now differentiates between GET and HEAD methods, invoking the appropriate helper to ensure HEAD requests are served instantly with only the required metadata.
This commit is contained in:
@@ -94,4 +94,26 @@ object ChapterDownloadHelper {
|
||||
|
||||
return Triple(cbzFile.first, fileName, cbzFile.second)
|
||||
}
|
||||
|
||||
fun getCbzMetadataForDownload(chapterId: Int): Triple<String, Long, String> { // fileName, fileSize, contentType
|
||||
val (chapterData, mangaTitle) =
|
||||
transaction {
|
||||
val row =
|
||||
(ChapterTable innerJoin MangaTable)
|
||||
.select(ChapterTable.columns + MangaTable.columns)
|
||||
.where { ChapterTable.id eq chapterId }
|
||||
.firstOrNull() ?: throw IllegalArgumentException("ChapterId $chapterId not found")
|
||||
val chapter = ChapterTable.toDataClass(row)
|
||||
val title = row[MangaTable.title]
|
||||
Pair(chapter, title)
|
||||
}
|
||||
|
||||
val scanlatorPart = chapterData.scanlator?.let { "[$it] " } ?: ""
|
||||
val fileName = "$mangaTitle - $scanlatorPart${chapterData.name}.cbz"
|
||||
|
||||
val fileSize = provider(chapterData.mangaId, chapterData.id).getArchiveSize()
|
||||
val contentType = "application/vnd.comicbook+zip"
|
||||
|
||||
return Triple(fileName, fileSize, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,8 @@ abstract class ChaptersFilesProvider<Type : FileType>(
|
||||
|
||||
abstract fun getAsArchiveStream(): Pair<InputStream, Long>
|
||||
|
||||
abstract fun getArchiveSize(): Long
|
||||
|
||||
private fun maybeConvertPages(chapterCacheFolder: File) {
|
||||
val conversions = serverConfig.downloadConversions.value
|
||||
|
||||
|
||||
@@ -108,6 +108,11 @@ class ArchiveProvider(
|
||||
return cbzFile.inputStream() to cbzFile.length()
|
||||
}
|
||||
|
||||
override fun getArchiveSize(): Long {
|
||||
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||
return if (cbzFile.exists()) cbzFile.length() else 0L
|
||||
}
|
||||
|
||||
private fun extractCbzFile(
|
||||
cbzFile: File,
|
||||
chapterFolder: File,
|
||||
|
||||
@@ -101,4 +101,11 @@ class FolderProvider(
|
||||
val zipData = byteArrayOutputStream.toByteArray()
|
||||
return ByteArrayInputStream(zipData) to zipData.size.toLong()
|
||||
}
|
||||
|
||||
override fun getArchiveSize(): Long {
|
||||
val chapterDir = File(getChapterDownloadPath(mangaId, chapterId))
|
||||
if (!chapterDir.exists() || !chapterDir.isDirectory) return 0L
|
||||
// Approximation: actual CBZ size is slightly larger due to ZIP metadata, but sufficient for Content-Length header.
|
||||
return chapterDir.listFiles()?.filter { it.isFile }?.sumOf { it.length() } ?: 0L
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user