Feature/automatically download new chapters (#596)

* Automatically download new chapters

* Log queued downloads

* Add function to get number of manga chapters
This commit is contained in:
schroda
2023-07-20 23:47:46 +02:00
committed by GitHub
parent c1d702a51c
commit 8690e918dd
6 changed files with 59 additions and 5 deletions

View File

@@ -122,17 +122,21 @@ class ChapterMutation {
val (clientMutationId, mangaId) = input val (clientMutationId, mangaId) = input
return future { return future {
val numberOfCurrentChapters = Chapter.getCountOfMangaChapters(mangaId)
Chapter.fetchChapterList(mangaId) Chapter.fetchChapterList(mangaId)
}.thenApply { numberOfCurrentChapters
}.thenApply { numberOfCurrentChapters ->
val chapters = transaction { val chapters = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId } ChapterTable.select { ChapterTable.manga eq mangaId }
.orderBy(ChapterTable.sourceOrder) .orderBy(ChapterTable.sourceOrder)
.map { ChapterType(it) }
} }
// download new chapters if settings flag is enabled
Chapter.downloadNewChapters(mangaId, numberOfCurrentChapters, chapters.toList())
FetchChaptersPayload( FetchChaptersPayload(
clientMutationId = clientMutationId, clientMutationId = clientMutationId,
chapters = chapters chapters = chapters.map { ChapterType(it) }
) )
} }
} }

View File

@@ -12,8 +12,10 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import mu.KotlinLogging
import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.Op import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.SortOrder.ASC import org.jetbrains.exposed.sql.SortOrder.ASC
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@@ -24,6 +26,8 @@ import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.manga.impl.Manga.getManga import suwayomi.tachidesk.manga.impl.Manga.getManga
import suwayomi.tachidesk.manga.impl.download.DownloadManager
import suwayomi.tachidesk.manga.impl.download.DownloadManager.EnqueueInput
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
@@ -35,9 +39,12 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.manga.model.table.PageTable
import suwayomi.tachidesk.manga.model.table.toDataClass import suwayomi.tachidesk.manga.model.table.toDataClass
import suwayomi.tachidesk.server.serverConfig
import java.time.Instant import java.time.Instant
object Chapter { object Chapter {
private val logger = KotlinLogging.logger { }
/** get chapter list when showing a manga */ /** get chapter list when showing a manga */
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean = false): List<ChapterDataClass> { suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean = false): List<ChapterDataClass> {
return if (onlineFetch) { return if (onlineFetch) {
@@ -55,6 +62,10 @@ object Chapter {
} }
} }
fun getCountOfMangaChapters(mangaId: Int): Int {
return transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count().toInt() }
}
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> { private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
val chapterList = fetchChapterList(mangaId) val chapterList = fetchChapterList(mangaId)
@@ -106,6 +117,7 @@ object Chapter {
url = manga.url url = manga.url
} }
val numberOfCurrentChapters = getCountOfMangaChapters(mangaId)
val chapterList = source.fetchChapterList(sManga).awaitSingle() val chapterList = source.fetchChapterList(sManga).awaitSingle()
// Recognize number for new chapters. // Recognize number for new chapters.
@@ -157,8 +169,13 @@ object Chapter {
} }
} }
val newChapters = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId }
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList()
}
// clear any orphaned/duplicate chapters that are in the db but not in `chapterList` // clear any orphaned/duplicate chapters that are in the db but not in `chapterList`
val dbChapterCount = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count() } val dbChapterCount = newChapters.count()
if (dbChapterCount > chapterList.size) { // we got some clean up due if (dbChapterCount > chapterList.size) { // we got some clean up due
val dbChapterList = transaction { val dbChapterList = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId } ChapterTable.select { ChapterTable.manga eq mangaId }
@@ -179,9 +196,39 @@ object Chapter {
} }
} }
downloadNewChapters(mangaId, numberOfCurrentChapters, newChapters)
return chapterList return chapterList
} }
fun downloadNewChapters(mangaId: Int, prevNumberOfChapters: Int, newChapters: List<ResultRow>) {
// convert numbers to be index based
val currentNumberOfChapters = (prevNumberOfChapters - 1).coerceAtLeast(0)
val updatedNumberOfChapters = (newChapters.size - 1).coerceAtLeast(0)
val areNewChaptersAvailable = currentNumberOfChapters < updatedNumberOfChapters
val wasInitialFetch = currentNumberOfChapters == 0
// make sure to ignore initial fetch
val downloadNewChapters = serverConfig.autoDownloadNewChapters && !wasInitialFetch && areNewChaptersAvailable
if (!downloadNewChapters) {
return
}
val numberOfNewChapters = updatedNumberOfChapters - currentNumberOfChapters
val chapterIdsToDownload = newChapters.subList(0, numberOfNewChapters)
.filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] }.map { it[ChapterTable.id].value }
if (chapterIdsToDownload.isEmpty()) {
return
}
logger.info { "downloadNewChapters($mangaId): Downloading \"${chapterIdsToDownload.size}\" new chapter(s)..." }
DownloadManager.enqueue(EnqueueInput(chapterIdsToDownload))
DownloadManager.start()
}
fun modifyChapter( fun modifyChapter(
mangaId: Int, mangaId: Int,
chapterIndex: Int, chapterIndex: Int,

View File

@@ -122,7 +122,7 @@ object DownloadManager {
scope.launch { scope.launch {
downloaderWatch.sample(1.seconds).collect { downloaderWatch.sample(1.seconds).collect {
val runningDownloaders = downloaders.values.filter { it.isActive } val runningDownloaders = downloaders.values.filter { it.isActive }
logger.info { "Running: ${runningDownloaders.size}" } logger.info { "Running: ${runningDownloaders.size}, Queued: ${downloadQueue.size}" }
if (runningDownloaders.size < MAX_SOURCES_IN_PARAllEL) { if (runningDownloaders.size < MAX_SOURCES_IN_PARAllEL) {
downloadQueue.asSequence() downloadQueue.asSequence()
.map { it.manga.sourceId.toLong() } .map { it.manga.sourceId.toLong() }

View File

@@ -32,6 +32,7 @@ class ServerConfig(getConfig: () -> Config, moduleName: String = MODULE_NAME) :
// downloader // downloader
var downloadAsCbz: Boolean by overridableConfig var downloadAsCbz: Boolean by overridableConfig
var downloadsPath: String by overridableConfig var downloadsPath: String by overridableConfig
var autoDownloadNewChapters: Boolean by overridableConfig
// updater // updater
var maxParallelUpdateRequests: Int by overridableConfig var maxParallelUpdateRequests: Int by overridableConfig

View File

@@ -17,6 +17,7 @@ server.electronPath = ""
# downloader # downloader
server.downloadAsCbz = false server.downloadAsCbz = false
server.downloadsPath = "" server.downloadsPath = ""
server.autoDownloadNewChapters = false # if new chapters that have been retrieved should get automatically downloaded
# updater # updater
server.maxParallelUpdateRequests = 10 # sets how many sources can be updated in parallel. updates are grouped by source and all mangas of a source are updated synchronously server.maxParallelUpdateRequests = 10 # sets how many sources can be updated in parallel. updates are grouped by source and all mangas of a source are updated synchronously

View File

@@ -9,6 +9,7 @@ server.socksProxyPort = ""
# downloader # downloader
server.downloadAsCbz = false server.downloadAsCbz = false
server.autoDownloadNewChapters = false
# updater # updater
server.maxParallelUpdateRequests = 10 server.maxParallelUpdateRequests = 10