Decouple Cache and Download behaviour (#493)

* Separate cache dir from download dir

* Move downloader logic outside of caching/image download logic

* remove unnecessary method duplication

* moved download logic inside download provider

* optimize and handle partial downloads

* made code review changes
This commit is contained in:
akabhirav
2023-02-12 20:26:26 +05:30
committed by GitHub
parent a027d6df1b
commit b10062c73d
9 changed files with 188 additions and 62 deletions

View File

@@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.impl.util
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.kodein.di.DI
@@ -21,17 +22,16 @@ import java.io.File
private val applicationDirs by DI.global.instance<ApplicationDirs>()
fun getMangaDir(mangaId: Int): String {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
fun getMangaDir(mangaId: Int, cache: Boolean = false): String {
val mangaEntry = getMangaEntry(mangaId)
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
val sourceDir = source.toString()
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
return "${applicationDirs.mangaDownloadsRoot}/$sourceDir/$mangaDir"
return (if (cache) applicationDirs.cacheRoot else applicationDirs.mangaDownloadsRoot) + "/$sourceDir/$mangaDir"
}
fun getChapterDir(mangaId: Int, chapterId: Int): String {
fun getChapterDir(mangaId: Int, chapterId: Int, cache: Boolean = false): String {
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() }
val chapterDir = SafePath.buildValidFilename(
@@ -41,12 +41,12 @@ fun getChapterDir(mangaId: Int, chapterId: Int): String {
}
)
return getMangaDir(mangaId) + "/$chapterDir"
return getMangaDir(mangaId, cache) + "/$chapterDir"
}
/** return value says if rename/move was successful */
fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
val mangaEntry = getMangaEntry(mangaId)
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
val sourceDir = source.toString()
@@ -66,3 +66,7 @@ fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
true
}
}
private fun getMangaEntry(mangaId: Int): ResultRow {
return transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
}

View File

@@ -9,6 +9,7 @@ package suwayomi.tachidesk.manga.impl.util.storage
import okhttp3.Response
import okhttp3.internal.closeQuietly
import suwayomi.tachidesk.manga.impl.util.getChapterDir
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
@@ -29,7 +30,7 @@ object ImageResponse {
}
/** fetch a cached image response, calls `fetcher` if cache fails */
suspend fun getCachedImageResponse(saveDir: String, fileName: String, fetcher: suspend () -> Response): Pair<InputStream, String> {
private suspend fun getCachedImageResponse(saveDir: String, fileName: String, fetcher: suspend () -> Response): Pair<InputStream, String> {
val cachedFile = findFileNameStartingWith(saveDir, fileName)
val filePath = "$saveDir/$fileName"
if (cachedFile != null) {
@@ -43,19 +44,7 @@ object ImageResponse {
val response = fetcher()
if (response.code == 200) {
val tmpSavePath = "$filePath.tmp"
val tmpSaveFile = File(tmpSavePath)
response.body!!.source().saveTo(tmpSaveFile)
// find image type
val imageType = response.headers["content-type"]
?: ImageUtil.findImageType { tmpSaveFile.inputStream() }?.mime
?: "image/jpeg"
val actualSavePath = "$filePath.${imageType.substringAfter("/")}"
tmpSaveFile.renameTo(File(actualSavePath))
val (actualSavePath, imageType) = saveImage(filePath, response.body!!.byteStream())
return pathToInputStream(actualSavePath) to imageType
} else {
response.closeQuietly()
@@ -63,6 +52,21 @@ object ImageResponse {
}
}
fun saveImage(filePath: String, image: InputStream): Pair<String, String> {
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 Pair(actualSavePath, imageType)
}
fun clearCachedImage(saveDir: String, fileName: String) {
val cachedFile = findFileNameStartingWith(saveDir, fileName)
cachedFile?.also {
@@ -70,7 +74,7 @@ object ImageResponse {
}
}
suspend fun getNoCacheImageResponse(fetcher: suspend () -> Response): Pair<InputStream, String> {
private suspend fun getNoCacheImageResponse(fetcher: suspend () -> Response): Pair<InputStream, String> {
val response = fetcher()
if (response.code == 200) {
@@ -88,11 +92,20 @@ object ImageResponse {
}
}
suspend fun getImageResponse(saveDir: String, fileName: String, useCache: Boolean = false, fetcher: suspend () -> Response): Pair<InputStream, String> {
suspend fun getImageResponse(saveDir: String, fileName: String, useCache: Boolean, fetcher: suspend () -> Response): Pair<InputStream, String> {
return if (useCache) {
getCachedImageResponse(saveDir, fileName, fetcher)
} else {
getNoCacheImageResponse(fetcher)
}
}
suspend fun getImageResponse(mangaId: Int, chapterId: Int, fileName: String, useCache: Boolean, fetcher: suspend () -> Response): Pair<InputStream, String> {
var saveDir = ""
if (useCache) {
saveDir = getChapterDir(mangaId, chapterId, true)
File(saveDir).mkdirs()
}
return getImageResponse(saveDir, fileName, useCache, fetcher)
}
}