diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt index f7646767e..01092db57 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt @@ -1,7 +1,16 @@ package suwayomi.tachidesk.manga.impl.download.fileProvider 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.util.getChapterCachePath +import java.io.File import java.io.InputStream /* @@ -14,11 +23,46 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow return RetrieveFile1Args(::getImageImpl) } - abstract suspend fun downloadImpl( + @OptIn(FlowPreview::class) + open suspend fun downloadImpl( download: DownloadChapter, scope: CoroutineScope, 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 Unit> { return FileDownload3Args(::downloadImpl) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt index fd3c60e68..5b021dc7d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt @@ -9,8 +9,8 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream import org.apache.commons.compress.archivers.zip.ZipFile import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider 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.getChapterDownloadPath import java.io.File import java.io.InputStream @@ -29,20 +29,19 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang 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) + val chapterCacheFolder = File(getChapterCachePath(mangaId, chapterId)) + if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterCacheFolder) - FolderProvider(mangaId, chapterId).download().execute(download, scope, step) + super.downloadImpl(download, scope, step) withContext(Dispatchers.IO) { outputFile.createNewFile() } ZipArchiveOutputStream(outputFile.outputStream()).use { zipOut -> - if (chapterFolder.isDirectory) { - chapterFolder.listFiles()?.sortedBy { it.name }?.forEach { + if (chapterCacheFolder.isDirectory) { + chapterCacheFolder.listFiles()?.sortedBy { it.name }?.forEach { val entry = ZipArchiveEntry(it.name) try { zipOut.putArchiveEntry(entry) @@ -56,8 +55,8 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang } } - if (chapterFolder.exists() && chapterFolder.isDirectory) { - chapterFolder.deleteRecursively() + if (chapterCacheFolder.exists() && chapterCacheFolder.isDirectory) { + chapterCacheFolder.deleteRecursively() } return true diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt index 7ae570232..8f217fa22 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt @@ -1,18 +1,10 @@ package suwayomi.tachidesk.manga.impl.download.fileProvider.impl 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.model.DownloadChapter +import suwayomi.tachidesk.manga.impl.util.getChapterCachePath import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath -import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse import java.io.File import java.io.FileInputStream import java.io.InputStream @@ -30,47 +22,28 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga return Pair(FileInputStream(file).buffered(), "image/$fileType") } - @OptIn(FlowPreview::class) override suspend fun downloadImpl( download: DownloadChapter, scope: CoroutineScope, step: suspend (DownloadChapter?, Boolean) -> Unit ): Boolean { - val pageCount = download.chapter.pageCount val chapterDir = getChapterDownloadPath(mangaId, chapterId) val folder = File(chapterDir) - folder.mkdirs() - for (pageNum in 0 until pageCount) { - var pageProgressJob: Job? = null - val fileName = getPageName(pageNum) // might have to change this to index stored in database - 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 alreadyDownloaded = folder.exists() + if (alreadyDownloaded) { + return true + } + + val downloadSucceeded = super.downloadImpl(download, scope, step) + if (!downloadSucceeded) { + return false } + + folder.mkdirs() + val cacheChapterDir = getChapterCachePath(mangaId, chapterId) + File(cacheChapterDir).renameTo(folder) + return true }