diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index fa87392930..02f41b52d0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -15,7 +15,6 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SortOrder.ASC import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.manga.impl.Manga.getManga import suwayomi.tachidesk.manga.impl.util.getChapterDir @@ -312,9 +311,7 @@ object Chapter { ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) } .first()[ChapterTable.id].value - val chapterDir = getChapterDir(mangaId, chapterId) - - File(chapterDir).deleteRecursively() + ChapterDownloadHelper.delete(mangaId, chapterId) ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }) { it[isDownloaded] = false diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt new file mode 100644 index 0000000000..f81d899a76 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt @@ -0,0 +1,25 @@ +package suwayomi.tachidesk.manga.impl + +import suwayomi.tachidesk.manga.impl.download.DownloadedFilesProvider +import suwayomi.tachidesk.manga.impl.download.FolderProvider +import java.io.InputStream + +object ChapterDownloadHelper { + fun getImage(mangaId: Int, chapterId: Int, index: Int): Pair { + return provider(mangaId, chapterId).getImage(index) + } + + fun delete(mangaId: Int, chapterId: Int): Boolean { + return provider(mangaId, chapterId).delete() + } + + fun putImage(mangaId: Int, chapterId: Int, index: Int, image: InputStream): Boolean { + return provider(mangaId, chapterId).putImage(index, image) + } + + // 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 { + return FolderProvider(mangaId, chapterId) + } + +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt index 41434f6b71..4a146b2459 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt @@ -15,7 +15,6 @@ import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update -import suwayomi.tachidesk.manga.impl.util.getChapterDir import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse @@ -82,10 +81,12 @@ object Page { } } - val chapterDir = getChapterDir(mangaId, chapterId) - File(chapterDir).mkdirs() val fileName = getPageName(index) + if (chapterEntry[ChapterTable.isDownloaded]) { + return ChapterDownloadHelper.getImage(mangaId, chapterId, index) + } + return getImageResponse(mangaId, chapterId, fileName, useCache) { source.fetchImage(tachiyomiPage).awaitSingle() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadedFilesProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadedFilesProvider.kt new file mode 100644 index 0000000000..5be1a98445 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadedFilesProvider.kt @@ -0,0 +1,18 @@ +package suwayomi.tachidesk.manga.impl.download + +import java.io.InputStream + +/* +* Base class for downloaded chapter files provider, example: Folder, Archive +* */ +abstract class DownloadedFilesProvider(val mangaId: Int, val chapterId: Int) { + abstract fun getImage(index: Int): Pair + + abstract fun putImage(index: Int, image: InputStream): Boolean + + abstract fun delete(): Boolean + + private fun getPageName(index: Int): String { + return String.format("%03d", index + 1) + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt index c1cb198335..a2d66c5ccd 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt @@ -23,6 +23,7 @@ import mu.KotlinLogging import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update +import suwayomi.tachidesk.manga.impl.ChapterDownloadHelper import suwayomi.tachidesk.manga.impl.Page.getPageImage import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter @@ -99,7 +100,7 @@ class Downloader( for (pageNum in 0 until pageCount) { var pageProgressJob: Job? = null try { - getPageImage( + val image = getPageImage( mangaId = download.mangaId, chapterIndex = download.chapterIndex, index = pageNum, @@ -113,7 +114,9 @@ class Downloader( } .launchIn(scope) } - ).first.close() + ).first + ChapterDownloadHelper.putImage(download.mangaId, download.chapter.id, pageNum, image) + image.close() } finally { // always cancel the page progress job even if it throws an exception to avoid memory leaks pageProgressJob?.cancel() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/FolderProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/FolderProvider.kt new file mode 100644 index 0000000000..2409fee3b7 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/FolderProvider.kt @@ -0,0 +1,47 @@ +package suwayomi.tachidesk.manga.impl.download + +import suwayomi.tachidesk.manga.impl.Page.getPageName +import suwayomi.tachidesk.manga.impl.util.getChapterDir +import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil +import java.io.File +import java.io.FileInputStream +import java.io.InputStream + +/* +* Provides downloaded files when pages were downloaded into folders +* */ +class FolderProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(mangaId, chapterId) { + override fun getImage(index: Int): Pair { + val chapterDir = getChapterDir(mangaId, chapterId) + val folder = File(chapterDir) + folder.mkdirs() + val file = folder.listFiles()?.get(index) + val fileType = file!!.name.substringAfterLast(".") + return Pair(FileInputStream(file).buffered(), "image/$fileType") + } + + override fun putImage(index: Int, image: InputStream): Boolean { + val chapterDir = getChapterDir(mangaId, chapterId) + val folder = File(chapterDir) + folder.mkdirs() + val fileName = getPageName(index) + val filePath = "$chapterDir/$fileName" + val tmpSavePath = "$filePath.tmp" + val tmpSaveFile = File(tmpSavePath) + image.use { input -> tmpSaveFile.outputStream().use { output -> input.copyTo(output) } } + + // find image type + val imageType = ImageUtil.findImageType { tmpSaveFile.inputStream() }?.mime + ?: "image/jpeg" + + val actualSavePath = "$filePath.${imageType.substringAfter("/")}" + + tmpSaveFile.renameTo(File(actualSavePath)) + return true + } + + override fun delete(): Boolean { + val chapterDir = getChapterDir(mangaId, chapterId) + return File(chapterDir).deleteRecursively() + } +}