Download as CBZ (#490)

* Download as CBZ

* Better error handling for zips (code review changes)
This commit is contained in:
akabhirav
2023-02-13 00:45:58 +05:30
committed by GitHub
parent 544bf2ea21
commit 555f73b478
7 changed files with 118 additions and 1 deletions

View File

@@ -1,9 +1,14 @@
package suwayomi.tachidesk.manga.impl
import kotlinx.coroutines.CoroutineScope
import suwayomi.tachidesk.manga.impl.download.ArchiveProvider
import suwayomi.tachidesk.manga.impl.download.DownloadedFilesProvider
import suwayomi.tachidesk.manga.impl.download.FolderProvider
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
import suwayomi.tachidesk.server.serverConfig
import java.io.File
import java.io.InputStream
object ChapterDownloadHelper {
@@ -27,6 +32,10 @@ object ChapterDownloadHelper {
// return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available
private fun provider(mangaId: Int, chapterId: Int): DownloadedFilesProvider {
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId)
if (!chapterFolder.exists() && serverConfig.downloadAsCbz) return ArchiveProvider(mangaId, chapterId)
return FolderProvider(mangaId, chapterId)
}
}

View File

@@ -16,6 +16,7 @@ import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.manga.impl.Page.getPageName
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
@@ -25,6 +26,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.PageTable
import suwayomi.tachidesk.manga.model.table.toDataClass
import java.io.File
suspend fun getChapterDownloadReady(chapterIndex: Int, mangaId: Int): ChapterDataClass {
val chapter = ChapterForDownload(chapterIndex, mangaId)
@@ -127,7 +129,10 @@ private class ChapterForDownload(
}
private fun isNotCompletelyDownloaded(): Boolean {
return !(chapterEntry[ChapterTable.isDownloaded] && firstPageExists())
return !(
chapterEntry[ChapterTable.isDownloaded] &&
(firstPageExists() || File(getChapterCbzPath(mangaId, chapterEntry[ChapterTable.id].value)).exists())
)
}
private fun firstPageExists(): Boolean {

View File

@@ -0,0 +1,89 @@
package suwayomi.tachidesk.manga.impl.download
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
import java.io.File
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class ArchiveProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(mangaId, chapterId) {
override fun getImage(index: Int): Pair<InputStream, String> {
val cbzPath = getChapterCbzPath(mangaId, chapterId)
val zipFile = ZipFile(cbzPath)
val zipEntry = zipFile.entries().toList().sortedWith(compareBy({ it.name }, { it.name }))[index]
val inputStream = zipFile.getInputStream(zipEntry)
val fileType = zipEntry.name.substringAfterLast(".")
return Pair(inputStream.buffered(), "image/$fileType")
}
override suspend fun download(
download: DownloadChapter,
scope: CoroutineScope,
step: suspend (DownloadChapter?, Boolean) -> Unit
): Boolean {
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
val chapterFolder = File(chapterDir)
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterFolder)
withContext(Dispatchers.IO) {
outputFile.createNewFile()
}
FolderProvider(mangaId, chapterId).download(download, scope, step)
ZipOutputStream(outputFile.outputStream()).use { zipOut ->
if (chapterFolder.isDirectory) {
chapterFolder.listFiles()?.sortedBy { it.name }?.forEach {
val entry = ZipEntry(it.name)
try {
zipOut.putNextEntry(entry)
it.inputStream().use { inputStream ->
inputStream.copyTo(zipOut)
}
} finally {
zipOut.closeEntry()
}
}
}
}
if (chapterFolder.exists() && chapterFolder.isDirectory) {
chapterFolder.deleteRecursively()
}
return true
}
override fun delete(): Boolean {
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
if (cbzFile.exists()) return cbzFile.delete()
return false
}
private fun handleExistingCbzFile(cbzFile: File, chapterFolder: File) {
if (!chapterFolder.exists()) chapterFolder.mkdirs()
ZipInputStream(cbzFile.inputStream()).use { zipInputStream ->
var zipEntry = zipInputStream.nextEntry
while (zipEntry != null) {
val file = File(chapterFolder, zipEntry.name)
if (!file.exists()) {
file.parentFile.mkdirs()
file.createNewFile()
}
file.outputStream().use { outputStream ->
zipInputStream.copyTo(outputStream)
}
zipEntry = zipInputStream.nextEntry
}
}
cbzFile.delete()
}
}

View File

@@ -47,6 +47,11 @@ private fun getChapterDir(mangaId: Int, chapterId: Int): String {
fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String {
return applicationDirs.mangaDownloadsRoot + "/" + getChapterDir(mangaId, chapterId)
}
fun getChapterCbzPath(mangaId: Int, chapterId: Int): String {
return getChapterDownloadPath(mangaId, chapterId) + ".cbz"
}
fun getChapterCachePath(mangaId: Int, chapterId: Int): String {
return applicationDirs.tempMangaCacheRoot + "/" + getChapterDir(mangaId, chapterId)
}

View File

@@ -23,6 +23,9 @@ class ServerConfig(config: Config, moduleName: String = MODULE_NAME) : SystemPro
val socksProxyHost: String by overridableConfig
val socksProxyPort: String by overridableConfig
// downloader
val downloadAsCbz: Boolean by overridableConfig
// misc
val debugLogsEnabled: Boolean = debugLogsEnabled(GlobalConfigManager.config)
val systemTrayEnabled: Boolean by overridableConfig

View File

@@ -19,6 +19,9 @@ server.basicAuthEnabled = false
server.basicAuthUsername = ""
server.basicAuthPassword = ""
# downloader
server.downloadAsCbz = false
# misc
server.debugLogsEnabled = false
server.systemTrayEnabled = true

View File

@@ -7,6 +7,9 @@ server.socksProxyEnabled = false
server.socksProxyHost = ""
server.socksProxyPort = ""
# downloader
server.downloadAsCbz = false
# misc
server.debugLogsEnabled = true
server.systemTrayEnabled = false