mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-22 12:32:34 +01:00
Download as CBZ (#490)
* Download as CBZ * Better error handling for zips (code review changes)
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
package suwayomi.tachidesk.manga.impl
|
package suwayomi.tachidesk.manga.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.ArchiveProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.DownloadedFilesProvider
|
import suwayomi.tachidesk.manga.impl.download.DownloadedFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.FolderProvider
|
import suwayomi.tachidesk.manga.impl.download.FolderProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
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
|
import java.io.InputStream
|
||||||
|
|
||||||
object ChapterDownloadHelper {
|
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
|
// 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 {
|
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)
|
return FolderProvider(mangaId, chapterId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.jetbrains.exposed.sql.select
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
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.getChapterDownloadPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
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.MangaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.PageTable
|
import suwayomi.tachidesk.manga.model.table.PageTable
|
||||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
suspend fun getChapterDownloadReady(chapterIndex: Int, mangaId: Int): ChapterDataClass {
|
suspend fun getChapterDownloadReady(chapterIndex: Int, mangaId: Int): ChapterDataClass {
|
||||||
val chapter = ChapterForDownload(chapterIndex, mangaId)
|
val chapter = ChapterForDownload(chapterIndex, mangaId)
|
||||||
@@ -127,7 +129,10 @@ private class ChapterForDownload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isNotCompletelyDownloaded(): Boolean {
|
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 {
|
private fun firstPageExists(): Boolean {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,11 @@ private fun getChapterDir(mangaId: Int, chapterId: Int): String {
|
|||||||
fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String {
|
fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String {
|
||||||
return applicationDirs.mangaDownloadsRoot + "/" + getChapterDir(mangaId, chapterId)
|
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 {
|
fun getChapterCachePath(mangaId: Int, chapterId: Int): String {
|
||||||
return applicationDirs.tempMangaCacheRoot + "/" + getChapterDir(mangaId, chapterId)
|
return applicationDirs.tempMangaCacheRoot + "/" + getChapterDir(mangaId, chapterId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ class ServerConfig(config: Config, moduleName: String = MODULE_NAME) : SystemPro
|
|||||||
val socksProxyHost: String by overridableConfig
|
val socksProxyHost: String by overridableConfig
|
||||||
val socksProxyPort: String by overridableConfig
|
val socksProxyPort: String by overridableConfig
|
||||||
|
|
||||||
|
// downloader
|
||||||
|
val downloadAsCbz: Boolean by overridableConfig
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
val debugLogsEnabled: Boolean = debugLogsEnabled(GlobalConfigManager.config)
|
val debugLogsEnabled: Boolean = debugLogsEnabled(GlobalConfigManager.config)
|
||||||
val systemTrayEnabled: Boolean by overridableConfig
|
val systemTrayEnabled: Boolean by overridableConfig
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ server.basicAuthEnabled = false
|
|||||||
server.basicAuthUsername = ""
|
server.basicAuthUsername = ""
|
||||||
server.basicAuthPassword = ""
|
server.basicAuthPassword = ""
|
||||||
|
|
||||||
|
# downloader
|
||||||
|
server.downloadAsCbz = false
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
server.debugLogsEnabled = false
|
server.debugLogsEnabled = false
|
||||||
server.systemTrayEnabled = true
|
server.systemTrayEnabled = true
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ server.socksProxyEnabled = false
|
|||||||
server.socksProxyHost = ""
|
server.socksProxyHost = ""
|
||||||
server.socksProxyPort = ""
|
server.socksProxyPort = ""
|
||||||
|
|
||||||
|
# downloader
|
||||||
|
server.downloadAsCbz = false
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
server.debugLogsEnabled = true
|
server.debugLogsEnabled = true
|
||||||
server.systemTrayEnabled = false
|
server.systemTrayEnabled = false
|
||||||
|
|||||||
Reference in New Issue
Block a user