Fix/chapter downloaded check (#1012)
* Properly check for first page in cbz files The download check for cbz files only checked if the archive existed but didn't check for the first page * Streamline getImageImpl of ChapterDownloadProviders * Exclude comic info file from page list In case the download folder did not contain any page files, only the comic info file existed, which caused the download check to incorrectly detect the first page * Add logging to ChapterForDownload#asDownloadReady
This commit is contained in:
@@ -41,7 +41,7 @@ object ChapterDownloadHelper {
|
||||
private fun provider(
|
||||
mangaId: Int,
|
||||
chapterId: Int,
|
||||
): ChaptersFilesProvider {
|
||||
): ChaptersFilesProvider<*> {
|
||||
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
|
||||
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||
if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId)
|
||||
|
||||
+32
-29
@@ -9,6 +9,8 @@ package suwayomi.tachidesk.manga.impl.chapter
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
@@ -17,17 +19,13 @@ import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
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.ChapterDownloadHelper
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.PageTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import java.io.File
|
||||
|
||||
suspend fun getChapterDownloadReady(
|
||||
chapterId: Int? = null,
|
||||
@@ -55,8 +53,25 @@ private class ChapterForDownload(
|
||||
optChapterIndex: Int? = null,
|
||||
optMangaId: Int? = null,
|
||||
) {
|
||||
var chapterEntry: ResultRow
|
||||
val chapterId: Int
|
||||
val chapterIndex: Int
|
||||
val mangaId: Int
|
||||
|
||||
val logger: KLogger
|
||||
|
||||
suspend fun asDownloadReady(): ChapterDataClass {
|
||||
if (isNotCompletelyDownloaded()) {
|
||||
val log = KotlinLogging.logger("${logger.name}::asDownloadReady")
|
||||
|
||||
val isMarkedAsDownloaded = chapterEntry[ChapterTable.isDownloaded]
|
||||
val doesFirstPageExist = firstPageExists()
|
||||
val isDownloaded = isMarkedAsDownloaded && doesFirstPageExist
|
||||
|
||||
log.debug { "isDownloaded= $isDownloaded (isMarkedAsDownloaded= $isMarkedAsDownloaded, doesFirstPageExist= $doesFirstPageExist)" }
|
||||
|
||||
if (!isDownloaded) {
|
||||
log.debug { "reset download status and fetch page list" }
|
||||
|
||||
markAsNotDownloaded()
|
||||
|
||||
val pageList = fetchPageList()
|
||||
@@ -69,16 +84,16 @@ private class ChapterForDownload(
|
||||
|
||||
private fun asDataClass() = ChapterTable.toDataClass(chapterEntry)
|
||||
|
||||
var chapterEntry: ResultRow
|
||||
val chapterId: Int
|
||||
val chapterIndex: Int
|
||||
val mangaId: Int
|
||||
|
||||
init {
|
||||
chapterEntry = freshChapterEntry(optChapterId, optChapterIndex, optMangaId)
|
||||
chapterId = chapterEntry[ChapterTable.id].value
|
||||
chapterIndex = chapterEntry[ChapterTable.sourceOrder]
|
||||
mangaId = chapterEntry[ChapterTable.manga].value
|
||||
|
||||
logger =
|
||||
KotlinLogging.logger(
|
||||
"${ChapterForDownload::class.java.name}(mangaId= $mangaId, chapterId= $chapterId, chapterIndex= $chapterIndex)",
|
||||
)
|
||||
}
|
||||
|
||||
private fun freshChapterEntry(
|
||||
@@ -151,24 +166,12 @@ private class ChapterForDownload(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNotCompletelyDownloaded(): Boolean {
|
||||
return !(
|
||||
chapterEntry[ChapterTable.isDownloaded] &&
|
||||
(firstPageExists() || File(getChapterCbzPath(mangaId, chapterEntry[ChapterTable.id].value)).exists())
|
||||
)
|
||||
}
|
||||
|
||||
private fun firstPageExists(): Boolean {
|
||||
val chapterId = chapterEntry[ChapterTable.id].value
|
||||
|
||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
||||
|
||||
println(chapterDir)
|
||||
println(getPageName(0))
|
||||
|
||||
return ImageResponse.findFileNameStartingWith(
|
||||
chapterDir,
|
||||
getPageName(0),
|
||||
) != null
|
||||
return try {
|
||||
ChapterDownloadHelper.getImage(mangaId, chapterId, 0).first.close()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-2
@@ -1,5 +1,6 @@
|
||||
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||
|
||||
import eu.kanade.tachiyomi.source.local.metadata.COMIC_INFO_FILE
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -7,6 +8,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.impl.Page
|
||||
@@ -20,11 +22,54 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
sealed class FileType {
|
||||
data class RegularFile(val file: File) : FileType()
|
||||
|
||||
data class ZipFile(val entry: ZipArchiveEntry) : FileType()
|
||||
|
||||
fun getName(): String {
|
||||
return when (this) {
|
||||
is FileType.RegularFile -> {
|
||||
this.file.name
|
||||
}
|
||||
is FileType.ZipFile -> {
|
||||
this.entry.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getExtension(): String {
|
||||
return when (this) {
|
||||
is FileType.RegularFile -> {
|
||||
this.file.extension
|
||||
}
|
||||
is FileType.ZipFile -> {
|
||||
this.entry.name.substringAfterLast(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Base class for downloaded chapter files provider, example: Folder, Archive
|
||||
*/
|
||||
abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : DownloadedFilesProvider {
|
||||
abstract fun getImageImpl(index: Int): Pair<InputStream, String>
|
||||
abstract class ChaptersFilesProvider<Type : FileType>(val mangaId: Int, val chapterId: Int) : DownloadedFilesProvider {
|
||||
protected abstract fun getImageFiles(): List<Type>
|
||||
|
||||
protected abstract fun getImageInputStream(image: Type): InputStream
|
||||
|
||||
fun getImageImpl(index: Int): Pair<InputStream, String> {
|
||||
val images = getImageFiles().filter { it.getName() != COMIC_INFO_FILE }.sortedBy { it.getName() }
|
||||
|
||||
if (images.isEmpty()) {
|
||||
throw Exception("no downloaded images found")
|
||||
}
|
||||
|
||||
val image = images[index]
|
||||
val imageFileType = image.getExtension()
|
||||
|
||||
return Pair(getImageInputStream(image).buffered(), "image/$imageFileType")
|
||||
}
|
||||
|
||||
override fun getImage(): RetrieveFile1Args<Int> {
|
||||
return RetrieveFile1Args(::getImageImpl)
|
||||
|
||||
+9
-8
@@ -10,6 +10,7 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.FileType
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
||||
import suwayomi.tachidesk.manga.impl.util.getMangaDownloadDir
|
||||
@@ -20,14 +21,14 @@ import java.io.InputStream
|
||||
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mangaId, chapterId) {
|
||||
override fun getImageImpl(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")
|
||||
class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider<FileType.ZipFile>(mangaId, chapterId) {
|
||||
override fun getImageFiles(): List<FileType.ZipFile> {
|
||||
val zipFile = ZipFile(getChapterCbzPath(mangaId, chapterId))
|
||||
return zipFile.entries.toList().map { FileType.ZipFile(it) }
|
||||
}
|
||||
|
||||
override fun getImageInputStream(image: FileType.ZipFile): InputStream {
|
||||
return ZipFile(getChapterCbzPath(mangaId, chapterId)).getInputStream(image.entry)
|
||||
}
|
||||
|
||||
override fun extractExistingDownload() {
|
||||
|
||||
+14
-9
@@ -4,27 +4,32 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.FileType.RegularFile
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.FileDeletionHelper
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
/*
|
||||
* Provides downloaded files when pages were downloaded into folders
|
||||
* */
|
||||
class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mangaId, chapterId) {
|
||||
override fun getImageImpl(index: Int): Pair<InputStream, String> {
|
||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
||||
val folder = File(chapterDir)
|
||||
folder.mkdirs()
|
||||
val file = folder.listFiles()?.sortedBy { it.name }?.get(index)
|
||||
val fileType = file!!.name.substringAfterLast(".")
|
||||
return Pair(FileInputStream(file).buffered(), "image/$fileType")
|
||||
class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider<RegularFile>(mangaId, chapterId) {
|
||||
override fun getImageFiles(): List<RegularFile> {
|
||||
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
|
||||
|
||||
if (!chapterFolder.exists()) {
|
||||
throw Exception("download folder does not exist")
|
||||
}
|
||||
|
||||
return chapterFolder.listFiles().orEmpty().toList().map(::RegularFile)
|
||||
}
|
||||
|
||||
override fun getImageInputStream(image: RegularFile): FileInputStream {
|
||||
return FileInputStream(image.file)
|
||||
}
|
||||
|
||||
override fun extractExistingDownload() {
|
||||
|
||||
Reference in New Issue
Block a user