Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download as CBZ #490

Merged
merged 2 commits into from
Feb 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions server/src/main/resources/server-reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ server.basicAuthEnabled = false
server.basicAuthUsername = ""
server.basicAuthPassword = ""

# downloader
server.downloadAsCbz = false

# misc
server.debugLogsEnabled = false
server.systemTrayEnabled = true
Expand Down
3 changes: 3 additions & 0 deletions server/src/test/resources/server-reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ server.socksProxyEnabled = false
server.socksProxyHost = ""
server.socksProxyPort = ""

# downloader
server.downloadAsCbz = false

# misc
server.debugLogsEnabled = true
server.systemTrayEnabled = false
Expand Down