Implement automatic removal of downloads on Suwayomi after reading, configurable via extension settings (mihonapp/mihon#2673)

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 1263df9d4111511e49a43463c9808060433ce76d)
This commit is contained in:
Constantin Piber 2025-12-20 13:54:17 +01:00 committed by Cuong-Tran
parent 130aae8515
commit c525ce49af
3 changed files with 42 additions and 14 deletions

View file

@ -11,6 +11,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- `Other` - for technical stuff. - `Other` - for technical stuff.
## [Unreleased] ## [Unreleased]
### Added
- Automatically remove downloads on Suwayomi after reading, configurable via extension settings ([@cpiber](https://github.com/cpiber)) ([#2673](https://github.com/mihonapp/mihon/pull/2673))
### Improved ### Improved
- Minimize memory usage by reducing in-memory cover cache size ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2266](https://github.com/mihonapp/mihon/pull/2266)) - Minimize memory usage by reducing in-memory cover cache size ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2266](https://github.com/mihonapp/mihon/pull/2266))

View file

@ -23,6 +23,9 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
const val UNREAD = 1L const val UNREAD = 1L
const val READING = 2L const val READING = 2L
const val COMPLETED = 3L const val COMPLETED = 3L
private const val TRACKER_DELETE_KEY = "Tracker Delete"
private const val TRACKER_DELETE_DEFAULT = false
} }
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED) override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
@ -55,7 +58,7 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
} }
} }
return api.updateProgress(track) return api.updateProgress(track, getPrefTrackerDelete())
} }
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track { override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
@ -110,6 +113,11 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
return normalized.substringAfterLast('/').toLong() return normalized.substringAfterLast('/').toLong()
} }
private fun getPrefTrackerDelete(): Boolean {
val preferences = api.sourcePreferences()
return preferences.getBoolean(TRACKER_DELETE_KEY, TRACKER_DELETE_DEFAULT)
}
// KMK --> // KMK -->
override fun hasNotStartedReading(status: Long): Boolean = status == UNREAD override fun hasNotStartedReading(status: Long): Boolean = status == UNREAD
// KMK <-- // KMK <--

View file

@ -1,12 +1,15 @@
package eu.kanade.tachiyomi.data.track.suwayomi package eu.kanade.tachiyomi.data.track.suwayomi
import android.content.SharedPreferences
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.sourcePreferences
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addAll import kotlinx.serialization.json.addAll
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
@ -26,10 +29,13 @@ class SuwayomiApi(private val trackId: Long) {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val source: HttpSource by lazy { sourceManager.get(sourceId) as HttpSource } private val source: HttpSource by lazy { sourceManager.get(sourceId) as HttpSource }
private val configurableSource: ConfigurableSource by lazy { (sourceManager.get(sourceId) as ConfigurableSource) }
private val client: OkHttpClient by lazy { source.client } private val client: OkHttpClient by lazy { source.client }
private val baseUrl: String by lazy { source.baseUrl.trimEnd('/') } private val baseUrl: String by lazy { source.baseUrl.trimEnd('/') }
private val apiUrl: String by lazy { "$baseUrl/api/graphql" } private val apiUrl: String by lazy { "$baseUrl/api/graphql" }
public fun sourcePreferences(): SharedPreferences = configurableSource.sourcePreferences()
suspend fun getTrackSearch(mangaId: Long): TrackSearch = withIOContext { suspend fun getTrackSearch(mangaId: Long): TrackSearch = withIOContext {
val query = """ val query = """
|query GetManga(${'$'}mangaId: Int!) { |query GetManga(${'$'}mangaId: Int!) {
@ -76,9 +82,11 @@ class SuwayomiApi(private val trackId: Long) {
} }
} }
suspend fun updateProgress(track: Track): Track { suspend fun updateProgress(track: Track, deleteDownloadsOnServer: Boolean = false): Track {
val mangaId = track.remote_id val mangaId = track.remote_id
// TODO: Include a filter on the chapter number here
// Below, we only consider older chapters; since v2.1.1985 filtering works properly in the query
val chaptersQuery = """ val chaptersQuery = """
|query GetMangaUnreadChapters(${'$'}mangaId: Int!) { |query GetMangaUnreadChapters(${'$'}mangaId: Int!) {
| chapters(condition: {mangaId: ${'$'}mangaId, isRead: false}) { | chapters(condition: {mangaId: ${'$'}mangaId, isRead: false}) {
@ -110,15 +118,26 @@ class SuwayomiApi(private val trackId: Long) {
.mapNotNull { n -> n.id.takeIf { n.chapterNumber <= track.last_chapter_read } } .mapNotNull { n -> n.id.takeIf { n.chapterNumber <= track.last_chapter_read } }
} }
val markQuery = """ val markQuery = if (deleteDownloadsOnServer) {
"""
|mutation MarkChaptersRead(${'$'}chapters: [Int!]!) { |mutation MarkChaptersRead(${'$'}chapters: [Int!]!) {
| updateChapters(input: {ids: ${'$'}chapters, patch: {isRead: true}}) { | updateChapters(input: {ids: ${'$'}chapters, patch: {isRead: true}}) {
| chapters { | __typename
| id
| } | }
| deleteDownloadedChapters(input: {ids: ${'$'}chapters}) {
| __typename
| } | }
|} |}
""".trimMargin() """.trimMargin()
} else {
"""
|mutation MarkChaptersRead(${'$'}chapters: [Int!]!) {
| updateChapters(input: {ids: ${'$'}chapters, patch: {isRead: true}}) {
| __typename
| }
|}
""".trimMargin()
}
val markPayload = buildJsonObject { val markPayload = buildJsonObject {
put("query", markQuery) put("query", markQuery)
putJsonObject("variables") { putJsonObject("variables") {
@ -140,9 +159,7 @@ class SuwayomiApi(private val trackId: Long) {
val trackQuery = """ val trackQuery = """
|mutation TrackManga(${'$'}mangaId: Int!) { |mutation TrackManga(${'$'}mangaId: Int!) {
| trackProgress(input: {mangaId: ${'$'}mangaId}) { | trackProgress(input: {mangaId: ${'$'}mangaId}) {
| trackRecords { | __typename
| lastChapterRead
| }
| } | }
|} |}
""".trimMargin() """.trimMargin()