mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 11:24:35 -05:00
Always return "ArchiveProvider" in case "downloadAsCbz" is enabled (#671)
* Move chapter download logic to base class * Do not reuse "FolderProvider" in "ArchiveProviders" download function Due to reusing the "FolderProvider" to download a chapter as a cbz file, a normal chapter download folder was created. In case the download was aborted before the cbz file got created and the folder deleted, the next time the chapter got downloaded, the wrong "FileProvider" was selected, causing the chapter not to get downloaded as a cbz file.
This commit is contained in:
@@ -1,7 +1,16 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.sample
|
||||||
|
import suwayomi.tachidesk.manga.impl.Page
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||||
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -14,11 +23,46 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
|
|||||||
return RetrieveFile1Args(::getImageImpl)
|
return RetrieveFile1Args(::getImageImpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract suspend fun downloadImpl(
|
@OptIn(FlowPreview::class)
|
||||||
|
open suspend fun downloadImpl(
|
||||||
download: DownloadChapter,
|
download: DownloadChapter,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
): Boolean
|
): Boolean {
|
||||||
|
val pageCount = download.chapter.pageCount
|
||||||
|
val chapterDir = getChapterCachePath(mangaId, chapterId)
|
||||||
|
val folder = File(chapterDir)
|
||||||
|
folder.mkdirs()
|
||||||
|
|
||||||
|
for (pageNum in 0 until pageCount) {
|
||||||
|
var pageProgressJob: Job? = null
|
||||||
|
val fileName = Page.getPageName(pageNum) // might have to change this to index stored in database
|
||||||
|
if (File(folder, fileName).exists()) continue
|
||||||
|
try {
|
||||||
|
Page.getPageImage(
|
||||||
|
mangaId = download.mangaId,
|
||||||
|
chapterIndex = download.chapterIndex,
|
||||||
|
index = pageNum
|
||||||
|
) { flow ->
|
||||||
|
pageProgressJob = flow
|
||||||
|
.sample(100)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.onEach {
|
||||||
|
download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount
|
||||||
|
step(null, false) // don't throw on canceled download here since we can't do anything
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// always cancel the page progress job even if it throws an exception to avoid memory leaks
|
||||||
|
pageProgressJob?.cancel()
|
||||||
|
}
|
||||||
|
// TODO: retry on error with 2,4,8 seconds of wait
|
||||||
|
download.progress = ((pageNum + 1).toFloat()) / pageCount
|
||||||
|
step(download, false)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun download(): FileDownload3Args<DownloadChapter, CoroutineScope, suspend (DownloadChapter?, Boolean) -> Unit> {
|
override fun download(): FileDownload3Args<DownloadChapter, CoroutineScope, suspend (DownloadChapter?, Boolean) -> Unit> {
|
||||||
return FileDownload3Args(::downloadImpl)
|
return FileDownload3Args(::downloadImpl)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
|||||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@@ -29,20 +29,19 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
|
|||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
|
||||||
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
|
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||||
val chapterFolder = File(chapterDir)
|
val chapterCacheFolder = File(getChapterCachePath(mangaId, chapterId))
|
||||||
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterFolder)
|
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterCacheFolder)
|
||||||
|
|
||||||
FolderProvider(mangaId, chapterId).download().execute(download, scope, step)
|
super.downloadImpl(download, scope, step)
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
outputFile.createNewFile()
|
outputFile.createNewFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipArchiveOutputStream(outputFile.outputStream()).use { zipOut ->
|
ZipArchiveOutputStream(outputFile.outputStream()).use { zipOut ->
|
||||||
if (chapterFolder.isDirectory) {
|
if (chapterCacheFolder.isDirectory) {
|
||||||
chapterFolder.listFiles()?.sortedBy { it.name }?.forEach {
|
chapterCacheFolder.listFiles()?.sortedBy { it.name }?.forEach {
|
||||||
val entry = ZipArchiveEntry(it.name)
|
val entry = ZipArchiveEntry(it.name)
|
||||||
try {
|
try {
|
||||||
zipOut.putArchiveEntry(entry)
|
zipOut.putArchiveEntry(entry)
|
||||||
@@ -56,8 +55,8 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chapterFolder.exists() && chapterFolder.isDirectory) {
|
if (chapterCacheFolder.exists() && chapterCacheFolder.isDirectory) {
|
||||||
chapterFolder.deleteRecursively()
|
chapterCacheFolder.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.flow.sample
|
|
||||||
import suwayomi.tachidesk.manga.impl.Page
|
|
||||||
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
|
||||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -30,47 +22,28 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga
|
|||||||
return Pair(FileInputStream(file).buffered(), "image/$fileType")
|
return Pair(FileInputStream(file).buffered(), "image/$fileType")
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
|
||||||
override suspend fun downloadImpl(
|
override suspend fun downloadImpl(
|
||||||
download: DownloadChapter,
|
download: DownloadChapter,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val pageCount = download.chapter.pageCount
|
|
||||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
||||||
val folder = File(chapterDir)
|
val folder = File(chapterDir)
|
||||||
folder.mkdirs()
|
|
||||||
|
|
||||||
for (pageNum in 0 until pageCount) {
|
val alreadyDownloaded = folder.exists()
|
||||||
var pageProgressJob: Job? = null
|
if (alreadyDownloaded) {
|
||||||
val fileName = getPageName(pageNum) // might have to change this to index stored in database
|
return true
|
||||||
if (isExistingFile(folder, fileName)) continue
|
|
||||||
try {
|
|
||||||
Page.getPageImage(
|
|
||||||
mangaId = download.mangaId,
|
|
||||||
chapterIndex = download.chapterIndex,
|
|
||||||
index = pageNum
|
|
||||||
) { flow ->
|
|
||||||
pageProgressJob = flow
|
|
||||||
.sample(100)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach {
|
|
||||||
download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount
|
|
||||||
step(null, false) // don't throw on canceled download here since we can't do anything
|
|
||||||
}
|
|
||||||
.launchIn(scope)
|
|
||||||
}.first.use { image ->
|
|
||||||
val filePath = "$chapterDir/$fileName"
|
|
||||||
ImageResponse.saveImage(filePath, image)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// always cancel the page progress job even if it throws an exception to avoid memory leaks
|
|
||||||
pageProgressJob?.cancel()
|
|
||||||
}
|
|
||||||
// TODO: retry on error with 2,4,8 seconds of wait
|
|
||||||
download.progress = ((pageNum + 1).toFloat()) / pageCount
|
|
||||||
step(download, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val downloadSucceeded = super.downloadImpl(download, scope, step)
|
||||||
|
if (!downloadSucceeded) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
folder.mkdirs()
|
||||||
|
val cacheChapterDir = getChapterCachePath(mangaId, chapterId)
|
||||||
|
File(cacheChapterDir).renameTo(folder)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user