add reportSyncEvent for SyncYomi service (#2110)

* add reportSyncEvent

* Update SyncYomiSyncService.kt
This commit is contained in:
herowinb
2026-06-18 09:46:21 +07:00
committed by GitHub
parent be5e3f022e
commit c8f5d83e9c

View File

@@ -4,11 +4,15 @@ import android.app.Application
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.PUT import eu.kanade.tachiyomi.network.PUT
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.javalin.http.HttpStatus import io.javalin.http.HttpStatus
import kotlinx.coroutines.CancellationException
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import okhttp3.Headers import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@@ -36,31 +40,66 @@ object SyncYomiSyncService {
message: String?, message: String?,
) : Exception(message) ) : Exception(message)
@Serializable
private data class SyncEvent(
val event: SyncEventStatus,
val device_Name: String? = null,
val message: String? = null,
)
@Serializable
private enum class SyncEventStatus {
SYNC_STARTED,
SYNC_SUCCESS,
SYNC_FAILED,
SYNC_ERROR,
SYNC_CANCELLED,
}
suspend fun doSync( suspend fun doSync(
syncData: SyncData, syncData: SyncData,
startDate: Instant, startDate: Instant,
setSyncState: (SyncManager.SyncState) -> Unit, setSyncState: (SyncManager.SyncState) -> Unit,
): Backup? { ): Backup? {
reportSyncEvent(SyncEventStatus.SYNC_STARTED)
setSyncState(SyncManager.SyncState.Downloading(startDate)) setSyncState(SyncManager.SyncState.Downloading(startDate))
val (remoteData, etag) = pullSyncData()
val finalSyncData = return try {
if (remoteData != null) { val (remoteData, etag) = pullSyncData()
require(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
logger.debug { "Try update remote data with ETag($etag)" } val finalSyncData =
setSyncState(SyncManager.SyncState.Merging(startDate)) if (remoteData != null) {
mergeSyncData(syncData, remoteData) require(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
} else { logger.debug { "Try update remote data with ETag($etag)" }
// init or overwrite remote data setSyncState(SyncManager.SyncState.Merging(startDate))
logger.debug { "Try overwrite remote data with ETag($etag)" } mergeSyncData(syncData, remoteData)
syncData } else {
// init or overwrite remote data
logger.debug { "Try overwrite remote data with ETag($etag)" }
syncData
}
if (finalSyncData.backup != null) {
setSyncState(SyncManager.SyncState.Uploading(startDate))
} }
if (finalSyncData.backup != null) { val success = pushSyncData(finalSyncData, etag)
setSyncState(SyncManager.SyncState.Uploading(startDate)) if (success) {
reportSyncEvent(SyncEventStatus.SYNC_SUCCESS)
} else {
reportSyncEvent(SyncEventStatus.SYNC_FAILED, "Failed to push sync data")
}
finalSyncData.backup
} catch (e: Exception) {
if (e is CancellationException) {
reportSyncEvent(SyncEventStatus.SYNC_CANCELLED, e.message)
throw e
}
logger.error { "Error syncing: ${e.message}" }
reportSyncEvent(SyncEventStatus.SYNC_ERROR, e.message)
throw e
} }
pushSyncData(finalSyncData, etag)
return finalSyncData.backup
} }
private suspend fun pullSyncData(): Pair<SyncData?, String> { private suspend fun pullSyncData(): Pair<SyncData?, String> {
@@ -122,8 +161,8 @@ object SyncYomiSyncService {
private suspend fun pushSyncData( private suspend fun pushSyncData(
syncData: SyncData, syncData: SyncData,
eTag: String, eTag: String,
) { ): Boolean {
val backup = syncData.backup ?: return val backup = syncData.backup ?: return true
val host = serverConfig.syncYomiHost.value val host = serverConfig.syncYomiHost.value
val apiKey = serverConfig.syncYomiApiKey.value val apiKey = serverConfig.syncYomiApiKey.value
@@ -160,7 +199,7 @@ object SyncYomiSyncService {
val response = client.newCall(uploadRequest).await() val response = client.newCall(uploadRequest).await()
if (response.isSuccessful) { return if (response.isSuccessful) {
val newETag = val newETag =
response.headers["ETag"] response.headers["ETag"]
?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag") ?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag")
@@ -169,12 +208,53 @@ object SyncYomiSyncService {
.putString("last_sync_etag", newETag) .putString("last_sync_etag", newETag)
.apply() .apply()
logger.debug { "SyncYomi sync completed" } logger.debug { "SyncYomi sync completed" }
true
} else if (response.code == HttpStatus.PRECONDITION_FAILED.code) { } else if (response.code == HttpStatus.PRECONDITION_FAILED.code) {
// other clients updated remote data, will try next time // other clients updated remote data, will try next time
logger.debug { "SyncYomi sync failed with 412" } logger.debug { "SyncYomi sync failed with 412" }
false
} else { } else {
val responseBody = response.body.string() val responseBody = response.body.string()
logger.error { "SyncError: $responseBody" } logger.error { "SyncError: $responseBody" }
false
}
}
private suspend fun reportSyncEvent(
event: SyncEventStatus,
message: String? = null,
) {
try {
val host = serverConfig.syncYomiHost.value
val apiKey = serverConfig.syncYomiApiKey.value
val url = "$host/api/sync/event"
val headers = Headers.Builder().add("X-API-Token", apiKey).build()
// Use a fixed server name.
val bodyObj =
SyncEvent(
event = event,
device_Name = "Suwayomi Server",
message = message,
)
val jsonBody = Json.encodeToString(SyncEvent.serializer(), bodyObj)
val requestBody = jsonBody.toRequestBody("application/json; charset=utf-8".toMediaType())
val request =
POST(
url = url,
headers = headers,
body = requestBody,
)
network.client
.newCall(request)
.await()
.close()
} catch (e: Exception) {
logger.error { "Failed to report sync event: ${e.message}" }
} }
} }