mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-20 19:42:37 +01:00
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:
@@ -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) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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() }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user