From be5e3f022e6950d53d5614f9ce4fc90f39c6007f Mon Sep 17 00:00:00 2001 From: Zeedif Date: Wed, 17 Jun 2026 20:41:31 -0600 Subject: [PATCH] feat(download): improve chapter download filenames (#2100) * feat(download): improve chapter download filenames * refactor(download): use SafePath helper for filename sanitization --- .../manga/impl/ChapterDownloadHelper.kt | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt index 9d203557f..ba1255ee4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt @@ -16,6 +16,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.toDataClass import suwayomi.tachidesk.server.serverConfig +import xyz.nulldev.androidcompat.util.SafePath import java.io.File import java.io.InputStream @@ -76,13 +77,46 @@ object ChapterDownloadHelper { .select(ChapterTable.columns + MangaTable.columns) .where { ChapterTable.id eq chapterId } .firstOrNull() ?: throw IllegalArgumentException("ChapterId $chapterId not found") + val chapter = ChapterTable.toDataClass(row) - val mangaTitle = row[MangaTable.title] + val mangaTitle = row[MangaTable.title].trim() - val scanlatorPart = chapter.scanlator?.let { "[$it] " } ?: "" - val fileName = "$mangaTitle - $scanlatorPart${chapter.name}.cbz" + val scanlatorName = chapter.scanlator?.trim()?.takeIf { it.isNotEmpty() } + val chapterName = chapter.name.trim().takeIf { it.isNotEmpty() } - Pair(chapter, fileName) + val fileName = + buildString { + append(mangaTitle) + append(" - ") + + if (chapterName != null) { + append(chapterName) + } else if (chapter.chapterNumber >= 0f) { + // chapterNumber is stored as Float, drop .0 for whole numbers. + val formatNumber = + if (chapter.chapterNumber % 1 == 0f) { + chapter.chapterNumber.toInt().toString() + } else { + chapter.chapterNumber.toString() + } + append("#$formatNumber") + } else { + // Fallback when neither name nor valid chapter number exists + append("#${chapter.index}") + } + + if (scanlatorName != null) { + append(" [") + append(scanlatorName) + append("]") + } + append(".cbz") + } + + // Sanitize filename for OS compatibility + val safeFileName = SafePath.buildValidFilename(fileName) + + Pair(chapter, safeFileName) } fun getCbzForDownload(