Skip to content

Commit

Permalink
优化更新数据源时的错误提示
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Dec 15, 2024
1 parent 6e4befb commit 7d249cc
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package me.him188.ani.app.domain.mediasource.subscription

import kotlinx.serialization.Serializable
import me.him188.ani.app.data.models.ApiFailure
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours

Expand All @@ -26,6 +27,7 @@ data class MediaSourceSubscription(
@Serializable
class UpdateError(
val message: String?,
val failure: ApiFailure? = null, // serialization compatibility
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,33 @@
package me.him188.ani.app.domain.mediasource.subscription

import io.ktor.client.HttpClient
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel
import io.ktor.utils.io.CancellationException
import io.ktor.http.HttpStatusCode.Companion.UnprocessableEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.serialization.json.io.decodeFromSource
import me.him188.ani.app.data.models.ApiResponse
import me.him188.ani.app.data.models.map
import me.him188.ani.app.data.models.runApiRequest
import me.him188.ani.app.data.repository.RepositoryException
import me.him188.ani.app.domain.mediasource.codec.MediaSourceCodecManager
import me.him188.ani.app.platform.currentAniBuildConfig
import me.him188.ani.utils.coroutines.withExceptionCollector
import me.him188.ani.utils.ktor.toSource
import kotlin.coroutines.cancellation.CancellationException

class MediaSourceSubscriptionRequester(
private val client: Flow<HttpClient>
) {
/**
* 执行网络请求, 下载新订阅数据. 遇到错误时将会返回 [ApiResponse.failure]
*/
@Throws(RepositoryException::class, CancellationException::class)
suspend fun request(
subscription: MediaSourceSubscription,
): ApiResponse<SubscriptionUpdateData> {
): SubscriptionUpdateData {
val clientInstance = client.first()

suspend fun HttpResponse.decode() = bodyAsChannel().toSource().use {
Expand All @@ -47,20 +49,24 @@ class MediaSourceSubscriptionRequester(
withExceptionCollector {
// 首先直连
try {
return ApiResponse.success(clientInstance.get(subscription.url).decode())
return clientInstance.get(subscription.url).decode()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
collect(e) // continue
}

// 失败则尝试代理服务
return runApiRequest {
return try {
clientInstance.get(currentAniBuildConfig.aniAuthServerUrl + "/v1/subs/proxy") {
parameter("url", subscription.url)
}.decode()
} catch (e: ClientRequestException) {
if (e.response.status == UnprocessableEntity) {
// not in whitelist
throwLast() // ignore this exception, throw the previous one
}
}.map {
it.decode()
throw e
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ package me.him188.ani.app.domain.mediasource.subscription

import kotlinx.coroutines.flow.first
import me.him188.ani.app.data.models.ApiFailure
import me.him188.ani.app.data.models.ApiResponse
import me.him188.ani.app.data.models.valueOrElse
import me.him188.ani.app.data.repository.RepositoryAuthorizationException
import me.him188.ani.app.data.repository.RepositoryException
import me.him188.ani.app.data.repository.RepositoryNetworkException
import me.him188.ani.app.data.repository.RepositoryRateLimitedException
import me.him188.ani.app.data.repository.RepositoryServiceUnavailableException
import me.him188.ani.app.data.repository.RepositoryUnknownException
import me.him188.ani.app.data.repository.media.MediaSourceSubscriptionRepository
import me.him188.ani.app.domain.media.fetch.MediaSourceManager
import me.him188.ani.app.domain.media.fetch.updateMediaSourceArguments
Expand Down Expand Up @@ -59,33 +63,47 @@ class MediaSourceSubscriptionUpdater(

logger.info { "Updating subscription: ${subscription.url}" }

kotlin.runCatching {
updateSubscription(subscription)
}.fold(
onSuccess = { count ->
this.subscriptions.update(subscription.subscriptionId) { old ->
old.copy(
lastUpdated = MediaSourceSubscription.LastUpdated(
currentTimeMillis,
mediaSourceCount = count,
error = null,
),
)
}
},
onFailure = { error ->
logger.error(error) { "Failed to update subscription ${subscription.url}" }
this.subscriptions.update(subscription.subscriptionId) { old ->
old.copy(
lastUpdated = MediaSourceSubscription.LastUpdated(
currentTimeMillis,
mediaSourceCount = null,
error = UpdateError(error.toString()),
),
suspend fun setResult(count: Int?, error: UpdateError? = null) {
this.subscriptions.update(subscription.subscriptionId) { old ->
old.copy(
lastUpdated = MediaSourceSubscription.LastUpdated(
currentTimeMillis,
mediaSourceCount = count,
error = error,
),
)
}
}

try {
val count = updateSubscription(subscription)
setResult(count)
} catch (e: CancellationException) {
throw e
} catch (e: RepositoryException) {
when (e) {
is RepositoryAuthorizationException ->
setResult(null, UpdateError(e.toString(), ApiFailure.Unauthorized))

is RepositoryNetworkException ->
setResult(null, UpdateError(e.toString(), ApiFailure.NetworkError))

is RepositoryRateLimitedException ->
setResult(
null,
UpdateError("请求过于频繁", null), // TODO: 2024/12/3 use ApiFailure.RateLimited
)
}
},
)

is RepositoryServiceUnavailableException ->
setResult(null, UpdateError(e.toString(), ApiFailure.ServiceUnavailable))

is RepositoryUnknownException ->
setResult(null, UpdateError(e.toString(), null))
}
} catch (e: Exception) {
logger.error(e) { "Failed to update subscription ${subscription.url}" }
setResult(null, UpdateError(e.toString(), null))
}
}

return subscriptions.minOf { subscription -> subscription.updatePeriod }
Expand All @@ -105,12 +123,9 @@ class MediaSourceSubscriptionUpdater(

}

@Throws(UpdateSubscriptionException::class, CancellationException::class)
@Throws(RepositoryException::class, CancellationException::class)
private suspend fun updateSubscription(subscription: MediaSourceSubscription): Int {
val updateData = requester.request(subscription).valueOrElse {
throw RequestFailureException(it)
}

val updateData = requester.request(subscription)
val newArguments = updateData.exportedMediaSourceDataList.mediaSources.mapNotNull {
runCatching {
NewArgument(it, codecManager.decode(it))
Expand Down Expand Up @@ -193,6 +208,3 @@ class MediaSourceSubscriptionUpdater(
}
}
}

sealed class UpdateSubscriptionException(override val message: String?) : Exception()
class RequestFailureException(apiFailure: ApiFailure) : UpdateSubscriptionException("Request failed: $apiFailure")
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.him188.ani.app.data.models.ApiFailure
import me.him188.ani.app.domain.mediasource.subscription.MediaSourceSubscription
import me.him188.ani.app.tools.MonoTasker
import me.him188.ani.app.tools.formatDateTime
Expand Down Expand Up @@ -299,9 +300,10 @@ private fun SettingsScope.SubscriptionItem(
private fun formatLastUpdated(lastUpdated: MediaSourceSubscription.LastUpdated?): String {
if (lastUpdated == null) return "还未更新"
val mediaSourceCount = lastUpdated.mediaSourceCount
val error = lastUpdated.error
return when {
lastUpdated.error != null || mediaSourceCount == null -> {
"${formatDateTime(lastUpdated.timeMillis)}更新失败:${lastUpdated.error?.message}"
error != null || mediaSourceCount == null -> {
"${formatDateTime(lastUpdated.timeMillis)}更新失败:${formatError(error)}"
}

else -> {
Expand All @@ -314,3 +316,13 @@ private fun formatLastUpdated(lastUpdated: MediaSourceSubscription.LastUpdated?)
// null -> "${formatDateTime(lastUpdated.timeMillis)}更新成功"
// }
}

private fun formatError(error: MediaSourceSubscription.UpdateError?): String {
if (error == null) return "未知错误"
val failre = error.failure ?: return error.message ?: "未知错误"
return when (failre) {
ApiFailure.NetworkError -> "网络错误,请检查网络连接"
ApiFailure.ServiceUnavailable -> "服务暂不可用,请稍后重试"
ApiFailure.Unauthorized -> "服务禁止访问,请联系服务提供商"
}
}

0 comments on commit 7d249cc

Please sign in to comment.