mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01:00
Feature/remove download ahead logic (#867)
* Remove download ahead logic Unnecessary on server side, should just be done by the client * Rename "autoDownloadAheadLimit" to "autoDownloadNewChaptersLimit" * Deprecate the old field * Update Stable WebUI * Update Stable WebUI --------- Co-authored-by: Syer10 <syer10@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,7 @@ const val MainClass = "suwayomi.tachidesk.MainKt"
|
|||||||
// should be bumped with each stable release
|
// should be bumped with each stable release
|
||||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.7.0"
|
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.7.0"
|
||||||
|
|
||||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1335"
|
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1397"
|
||||||
|
|
||||||
// counts commits on the current checked out branch
|
// counts commits on the current checked out branch
|
||||||
val getTachideskRevision = {
|
val getTachideskRevision = {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
import suwayomi.tachidesk.manga.impl.Chapter
|
||||||
import suwayomi.tachidesk.manga.impl.Manga
|
|
||||||
import suwayomi.tachidesk.manga.impl.download.DownloadManager
|
import suwayomi.tachidesk.manga.impl.download.DownloadManager
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.Status
|
import suwayomi.tachidesk.manga.impl.download.model.Status
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
@@ -269,20 +268,4 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DownloadAheadInput(
|
|
||||||
val clientMutationId: String? = null,
|
|
||||||
val mangaIds: List<Int> = emptyList(),
|
|
||||||
val latestReadChapterIds: List<Int>? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DownloadAheadPayload(val clientMutationId: String?)
|
|
||||||
|
|
||||||
fun downloadAhead(input: DownloadAheadInput): DownloadAheadPayload {
|
|
||||||
val (clientMutationId, mangaIds, latestReadChapterIds) = input
|
|
||||||
|
|
||||||
Manga.downloadAhead(mangaIds, latestReadChapterIds ?: emptyList())
|
|
||||||
|
|
||||||
return DownloadAheadPayload(clientMutationId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ class SettingsMutation {
|
|||||||
updateSetting(settings.downloadsPath, serverConfig.downloadsPath)
|
updateSetting(settings.downloadsPath, serverConfig.downloadsPath)
|
||||||
updateSetting(settings.autoDownloadNewChapters, serverConfig.autoDownloadNewChapters)
|
updateSetting(settings.autoDownloadNewChapters, serverConfig.autoDownloadNewChapters)
|
||||||
updateSetting(settings.excludeEntryWithUnreadChapters, serverConfig.excludeEntryWithUnreadChapters)
|
updateSetting(settings.excludeEntryWithUnreadChapters, serverConfig.excludeEntryWithUnreadChapters)
|
||||||
updateSetting(settings.autoDownloadAheadLimit, serverConfig.autoDownloadAheadLimit)
|
updateSetting(settings.autoDownloadAheadLimit, serverConfig.autoDownloadNewChaptersLimit) // deprecated
|
||||||
|
updateSetting(settings.autoDownloadNewChaptersLimit, serverConfig.autoDownloadNewChaptersLimit)
|
||||||
|
|
||||||
// extension
|
// extension
|
||||||
updateSetting(settings.extensionRepos, serverConfig.extensionRepos)
|
updateSetting(settings.extensionRepos, serverConfig.extensionRepos)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
package suwayomi.tachidesk.graphql.types
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||||
import suwayomi.tachidesk.server.ServerConfig
|
import suwayomi.tachidesk.server.ServerConfig
|
||||||
import suwayomi.tachidesk.server.serverConfig
|
import suwayomi.tachidesk.server.serverConfig
|
||||||
@@ -38,7 +39,13 @@ interface Settings : Node {
|
|||||||
val downloadsPath: String?
|
val downloadsPath: String?
|
||||||
val autoDownloadNewChapters: Boolean?
|
val autoDownloadNewChapters: Boolean?
|
||||||
val excludeEntryWithUnreadChapters: Boolean?
|
val excludeEntryWithUnreadChapters: Boolean?
|
||||||
|
|
||||||
|
@GraphQLDeprecated(
|
||||||
|
"Replaced with autoDownloadNewChaptersLimit",
|
||||||
|
replaceWith = ReplaceWith("autoDownloadNewChaptersLimit"),
|
||||||
|
)
|
||||||
val autoDownloadAheadLimit: Int?
|
val autoDownloadAheadLimit: Int?
|
||||||
|
val autoDownloadNewChaptersLimit: Int?
|
||||||
|
|
||||||
// extension
|
// extension
|
||||||
val extensionRepos: List<String>?
|
val extensionRepos: List<String>?
|
||||||
@@ -99,7 +106,12 @@ data class PartialSettingsType(
|
|||||||
override val downloadsPath: String?,
|
override val downloadsPath: String?,
|
||||||
override val autoDownloadNewChapters: Boolean?,
|
override val autoDownloadNewChapters: Boolean?,
|
||||||
override val excludeEntryWithUnreadChapters: Boolean?,
|
override val excludeEntryWithUnreadChapters: Boolean?,
|
||||||
|
@GraphQLDeprecated(
|
||||||
|
"Replaced with autoDownloadNewChaptersLimit",
|
||||||
|
replaceWith = ReplaceWith("autoDownloadNewChaptersLimit"),
|
||||||
|
)
|
||||||
override val autoDownloadAheadLimit: Int?,
|
override val autoDownloadAheadLimit: Int?,
|
||||||
|
override val autoDownloadNewChaptersLimit: Int?,
|
||||||
// extension
|
// extension
|
||||||
override val extensionRepos: List<String>?,
|
override val extensionRepos: List<String>?,
|
||||||
// requests
|
// requests
|
||||||
@@ -152,7 +164,12 @@ class SettingsType(
|
|||||||
override val downloadsPath: String,
|
override val downloadsPath: String,
|
||||||
override val autoDownloadNewChapters: Boolean,
|
override val autoDownloadNewChapters: Boolean,
|
||||||
override val excludeEntryWithUnreadChapters: Boolean,
|
override val excludeEntryWithUnreadChapters: Boolean,
|
||||||
|
@GraphQLDeprecated(
|
||||||
|
"Replaced with autoDownloadNewChaptersLimit",
|
||||||
|
replaceWith = ReplaceWith("autoDownloadNewChaptersLimit"),
|
||||||
|
)
|
||||||
override val autoDownloadAheadLimit: Int,
|
override val autoDownloadAheadLimit: Int,
|
||||||
|
override val autoDownloadNewChaptersLimit: Int,
|
||||||
// extension
|
// extension
|
||||||
override val extensionRepos: List<String>,
|
override val extensionRepos: List<String>,
|
||||||
// requests
|
// requests
|
||||||
@@ -204,7 +221,8 @@ class SettingsType(
|
|||||||
config.downloadsPath.value,
|
config.downloadsPath.value,
|
||||||
config.autoDownloadNewChapters.value,
|
config.autoDownloadNewChapters.value,
|
||||||
config.excludeEntryWithUnreadChapters.value,
|
config.excludeEntryWithUnreadChapters.value,
|
||||||
config.autoDownloadAheadLimit.value,
|
config.autoDownloadNewChaptersLimit.value, // deprecated
|
||||||
|
config.autoDownloadNewChaptersLimit.value,
|
||||||
// extension
|
// extension
|
||||||
config.extensionRepos.value,
|
config.extensionRepos.value,
|
||||||
// requests
|
// requests
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ object Chapter {
|
|||||||
"mangaId= $mangaId, " +
|
"mangaId= $mangaId, " +
|
||||||
"prevNumberOfChapters= $prevNumberOfChapters, " +
|
"prevNumberOfChapters= $prevNumberOfChapters, " +
|
||||||
"updatedChapterList= ${updatedChapterList.size}, " +
|
"updatedChapterList= ${updatedChapterList.size}, " +
|
||||||
"downloadAheadLimit= ${serverConfig.autoDownloadAheadLimit.value}" +
|
"autoDownloadNewChaptersLimit= ${serverConfig.autoDownloadNewChaptersLimit.value}" +
|
||||||
")",
|
")",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -389,8 +389,8 @@ object Chapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val firstChapterToDownloadIndex =
|
val firstChapterToDownloadIndex =
|
||||||
if (serverConfig.autoDownloadAheadLimit.value > 0) {
|
if (serverConfig.autoDownloadNewChaptersLimit.value > 0) {
|
||||||
(numberOfNewChapters - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0)
|
(numberOfNewChapters - serverConfig.autoDownloadNewChaptersLimit.value).coerceAtLeast(0)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import org.kodein.di.conf.global
|
|||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||||
import suwayomi.tachidesk.manga.impl.Source.getSource
|
import suwayomi.tachidesk.manga.impl.Source.getSource
|
||||||
import suwayomi.tachidesk.manga.impl.download.DownloadManager
|
|
||||||
import suwayomi.tachidesk.manga.impl.download.DownloadManager.EnqueueInput
|
|
||||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException
|
||||||
import suwayomi.tachidesk.manga.impl.track.Track
|
import suwayomi.tachidesk.manga.impl.track.Track
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||||
@@ -47,15 +45,11 @@ import suwayomi.tachidesk.manga.model.table.MangaStatus
|
|||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
import suwayomi.tachidesk.server.serverConfig
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Timer
|
|
||||||
import java.util.TimerTask
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger { }
|
private val logger = KotlinLogging.logger { }
|
||||||
|
|
||||||
@@ -339,104 +333,4 @@ object Manga {
|
|||||||
clearCachedImage(applicationDirs.tempThumbnailCacheRoot, fileName)
|
clearCachedImage(applicationDirs.tempThumbnailCacheRoot, fileName)
|
||||||
clearCachedImage(applicationDirs.thumbnailDownloadsRoot, fileName)
|
clearCachedImage(applicationDirs.thumbnailDownloadsRoot, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val downloadAheadQueue = ConcurrentHashMap<String, ConcurrentHashMap.KeySetView<Int, Boolean>>()
|
|
||||||
private var downloadAheadTimer: Timer? = null
|
|
||||||
|
|
||||||
private const val MANGAS_KEY = "mangaIds"
|
|
||||||
private const val CHAPTERS_KEY = "chapterIds"
|
|
||||||
|
|
||||||
fun downloadAhead(
|
|
||||||
mangaIds: List<Int>,
|
|
||||||
latestReadChapterIds: List<Int> = emptyList(),
|
|
||||||
) {
|
|
||||||
if (serverConfig.autoDownloadAheadLimit.value == 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateDownloadAheadQueue = { key: String, ids: List<Int> ->
|
|
||||||
val idSet = downloadAheadQueue[key] ?: ConcurrentHashMap.newKeySet()
|
|
||||||
idSet.addAll(ids)
|
|
||||||
downloadAheadQueue[key] = idSet
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDownloadAheadQueue(MANGAS_KEY, mangaIds)
|
|
||||||
updateDownloadAheadQueue(CHAPTERS_KEY, latestReadChapterIds)
|
|
||||||
|
|
||||||
// handle cases where this function gets called multiple times in quick succession.
|
|
||||||
// this could happen in case e.g. multiple chapters get marked as read without batching the operation
|
|
||||||
downloadAheadTimer?.cancel()
|
|
||||||
downloadAheadTimer =
|
|
||||||
Timer().apply {
|
|
||||||
schedule(
|
|
||||||
object : TimerTask() {
|
|
||||||
override fun run() {
|
|
||||||
downloadAheadChapters(
|
|
||||||
downloadAheadQueue[MANGAS_KEY]?.toList().orEmpty(),
|
|
||||||
downloadAheadQueue[CHAPTERS_KEY]?.toList().orEmpty(),
|
|
||||||
)
|
|
||||||
downloadAheadQueue.clear()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads the latest unread and not downloaded chapters for each passed manga id.
|
|
||||||
*
|
|
||||||
* To pass a specific chapter as the latest read chapter for a manga, it can be provided in the "latestReadChapterIds" list.
|
|
||||||
* This makes it possible to handle cases, where the actual latest read chapter isn't marked as read yet.
|
|
||||||
* E.g. the client marks a chapter as read and at the same time sends the "downloadAhead" mutation.
|
|
||||||
* In this case, the latest read chapter could potentially be the one, that just got send to get marked as read by the client.
|
|
||||||
* Without providing it in "latestReadChapterIds" it could be incorrectly included in the chapters, that will get downloaded.
|
|
||||||
*
|
|
||||||
* The latest read chapter will be considered the starting point.
|
|
||||||
* E.g.:
|
|
||||||
* - 20 chapters
|
|
||||||
* - chapter 15 marked as read
|
|
||||||
* - 16 - 20 marked as unread
|
|
||||||
* - 10 - 14 marked as unread
|
|
||||||
*
|
|
||||||
* will download the unread chapters starting from chapter 15
|
|
||||||
*/
|
|
||||||
private fun downloadAheadChapters(
|
|
||||||
mangaIds: List<Int>,
|
|
||||||
latestReadChapterIds: List<Int>,
|
|
||||||
) {
|
|
||||||
val mangaToLatestReadChapterIndex =
|
|
||||||
transaction {
|
|
||||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq true) }
|
|
||||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).groupBy { it[ChapterTable.manga].value }
|
|
||||||
}.mapValues { (_, chapters) -> chapters.firstOrNull()?.let { it[ChapterTable.sourceOrder] } ?: 0 }
|
|
||||||
|
|
||||||
val mangaToUnreadChaptersMap =
|
|
||||||
transaction {
|
|
||||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq false) }
|
|
||||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
|
||||||
.groupBy { it[ChapterTable.manga].value }
|
|
||||||
}
|
|
||||||
|
|
||||||
val chapterIdsToDownload =
|
|
||||||
mangaToUnreadChaptersMap.map { (mangaId, unreadChapters) ->
|
|
||||||
val latestReadChapterIndex = mangaToLatestReadChapterIndex[mangaId] ?: 0
|
|
||||||
val lastChapterToDownloadIndex =
|
|
||||||
unreadChapters.indexOfLast {
|
|
||||||
it[ChapterTable.sourceOrder] > latestReadChapterIndex &&
|
|
||||||
it[ChapterTable.id].value !in latestReadChapterIds
|
|
||||||
}
|
|
||||||
val unreadChaptersToConsider = unreadChapters.subList(0, lastChapterToDownloadIndex + 1)
|
|
||||||
val firstChapterToDownloadIndex =
|
|
||||||
(unreadChaptersToConsider.size - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0)
|
|
||||||
unreadChaptersToConsider.subList(firstChapterToDownloadIndex, lastChapterToDownloadIndex + 1)
|
|
||||||
.filter { !it[ChapterTable.isDownloaded] }
|
|
||||||
.map { it[ChapterTable.id].value }
|
|
||||||
}.flatten()
|
|
||||||
|
|
||||||
logger.info { "downloadAheadChapters: download chapters [${chapterIdsToDownload.joinToString(", ")}]" }
|
|
||||||
|
|
||||||
DownloadManager.dequeue(mangaIds, chapterIdsToDownload)
|
|
||||||
DownloadManager.enqueue(EnqueueInput(chapterIdsToDownload))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF
|
|||||||
val downloadsPath: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
|
val downloadsPath: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
|
||||||
val autoDownloadNewChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
val autoDownloadNewChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||||
val excludeEntryWithUnreadChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
val excludeEntryWithUnreadChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||||
val autoDownloadAheadLimit: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
|
val autoDownloadNewChaptersLimit: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
|
||||||
|
|
||||||
// extensions
|
// extensions
|
||||||
val extensionRepos: MutableStateFlow<List<String>> by OverrideConfigValues(StringConfigAdapter)
|
val extensionRepos: MutableStateFlow<List<String>> by OverrideConfigValues(StringConfigAdapter)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ server.downloadAsCbz = false
|
|||||||
server.downloadsPath = ""
|
server.downloadsPath = ""
|
||||||
server.autoDownloadNewChapters = false # if new chapters that have been retrieved should get automatically downloaded
|
server.autoDownloadNewChapters = false # if new chapters that have been retrieved should get automatically downloaded
|
||||||
server.excludeEntryWithUnreadChapters = true # ignore automatic chapter downloads of entries with unread chapters
|
server.excludeEntryWithUnreadChapters = true # ignore automatic chapter downloads of entries with unread chapters
|
||||||
server.autoDownloadAheadLimit = 0 # 0 to disable it - how many unread downloaded chapters should be available - if the limit is reached, new chapters won't be downloaded automatically. this limit will also be applied to the auto download of new chapters on an update
|
server.autoDownloadNewChaptersLimit = 0 # 0 to disable it - how many unread downloaded chapters should be available - if the limit is reached, new chapters won't be downloaded automatically. this limit will also be applied to the auto download of new chapters on an update
|
||||||
|
|
||||||
# extension repos
|
# extension repos
|
||||||
server.extensionRepos = [
|
server.extensionRepos = [
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ server.socksProxyPort = ""
|
|||||||
server.downloadAsCbz = false
|
server.downloadAsCbz = false
|
||||||
server.autoDownloadNewChapters = false
|
server.autoDownloadNewChapters = false
|
||||||
server.excludeEntryWithUnreadChapters = true
|
server.excludeEntryWithUnreadChapters = true
|
||||||
server.autoDownloadAheadLimit = 0
|
server.autoDownloadNewChaptersLimit = 0
|
||||||
|
|
||||||
# requests
|
# requests
|
||||||
server.maxSourcesInParallel = 10
|
server.maxSourcesInParallel = 10
|
||||||
|
|||||||
Reference in New Issue
Block a user