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

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
Constantin Piber
2025-12-20 13:54:17 +01:00
committed by GitHub
parent c96b6ae562
commit 1263df9d41
3 changed files with 42 additions and 14 deletions
+3
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))
@@ -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 {
@@ -102,4 +105,9 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
private fun String.getMangaId(): Long = private fun String.getMangaId(): Long =
this.substringAfterLast('/').toLong() this.substringAfterLast('/').toLong()
private fun getPrefTrackerDelete(): Boolean {
val preferences = api.sourcePreferences()
return preferences.getBoolean(TRACKER_DELETE_KEY, TRACKER_DELETE_DEFAULT)
}
} }
@@ -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
@@ -29,10 +32,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!) {
@@ -79,9 +85,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}) {
@@ -113,15 +121,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") {
@@ -143,9 +162,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()