mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-06-30 09:24:34 -05:00
add reportSyncEvent for SyncYomi service (#2110)
* add reportSyncEvent * Update SyncYomiSyncService.kt
This commit is contained in:
@@ -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}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user