feat(kosync): add mutations for manual progress push and pull (#1625)

Exposes the existing push and pull functionality from the KoreaderSyncService via the GraphQL API.

This change introduces two new mutations:
- `pushKoSyncProgress`: Manually sends the current chapter's reading progress to the KOReader sync server.
- `pullKoSyncProgress`: Manually fetches and applies the latest reading progress from the KOReader sync server.

These mutations enable clients and WebUIs to implement manual sync triggers, providing users with more direct control over their reading progress synchronization, similar to the functionality offered by the official KOReader plugin and other clients like Readest.
This commit is contained in:
Zeedif
2025-09-09 16:13:05 -06:00
committed by GitHub
parent 257e1dd03d
commit 275727ed90

View File

@@ -1,14 +1,21 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
import suwayomi.tachidesk.graphql.types.LogoutKoSyncAccountPayload
import suwayomi.tachidesk.graphql.types.SettingsType
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
import suwayomi.tachidesk.manga.impl.sync.KoreaderSyncService
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
@@ -53,4 +60,100 @@ class KoreaderSyncMutation {
settings = SettingsType(),
)
}
data class PushKoSyncProgressInput(
val clientMutationId: String? = null,
val chapterId: Int,
)
data class PushKoSyncProgressPayload(
val clientMutationId: String?,
val success: Boolean,
val chapter: ChapterType?,
)
fun pushKoSyncProgress(
dataFetchingEnvironment: DataFetchingEnvironment,
input: PushKoSyncProgressInput,
): CompletableFuture<DataFetcherResult<PushKoSyncProgressPayload?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
KoreaderSyncService.pushProgress(input.chapterId)
val chapter =
transaction {
ChapterTable
.selectAll()
.where { ChapterTable.id eq input.chapterId }
.firstOrNull()
?.let { ChapterType(it) }
}
PushKoSyncProgressPayload(
clientMutationId = input.clientMutationId,
success = true,
chapter = chapter,
)
}
}
data class PullKoSyncProgressInput(
val clientMutationId: String? = null,
val chapterId: Int,
)
data class PullKoSyncProgressPayload(
val clientMutationId: String?,
val chapter: ChapterType?,
val syncConflict: SyncConflictInfoType?,
)
fun pullKoSyncProgress(
dataFetchingEnvironment: DataFetchingEnvironment,
input: PullKoSyncProgressInput,
): CompletableFuture<DataFetcherResult<PullKoSyncProgressPayload?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
var syncConflictInfo: SyncConflictInfoType? = null
if (syncResult != null) {
if (syncResult.isConflict) {
syncConflictInfo =
SyncConflictInfoType(
deviceName = syncResult.device,
remotePage = syncResult.pageRead,
)
}
if (syncResult.shouldUpdate) {
transaction {
ChapterTable.update({ ChapterTable.id eq input.chapterId }) {
it[lastPageRead] = syncResult.pageRead
it[lastReadAt] = syncResult.timestamp
}
}
}
}
val chapter =
transaction {
ChapterTable
.selectAll()
.where { ChapterTable.id eq input.chapterId }
.firstOrNull()
?.let { ChapterType(it) }
}
PullKoSyncProgressPayload(
clientMutationId = input.clientMutationId,
chapter = chapter,
syncConflict = syncConflictInfo,
)
}
}
}