funkwhale-app-android/app/src/main/java/audio/funkwhale/ffa/repositories/HttpUpstream.kt

129 lines
4.0 KiB
Kotlin
Raw Normal View History

package audio.funkwhale.ffa.repositories
2019-08-19 16:50:33 +02:00
2021-07-23 14:10:13 +02:00
import android.content.Context
2019-08-19 16:50:33 +02:00
import android.net.Uri
import android.util.Log
import audio.funkwhale.ffa.model.FFAResponse
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.RefreshError
import audio.funkwhale.ffa.utils.Settings
import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.mustNormalizeUrl
2019-08-19 16:50:33 +02:00
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.ResponseDeserializable
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
import com.github.kittinunf.result.Result
import com.google.gson.Gson
2019-10-31 16:17:37 +01:00
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
2019-10-31 16:17:37 +01:00
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
2019-08-19 16:50:33 +02:00
import java.io.Reader
import java.lang.reflect.Type
import kotlin.math.ceil
class HttpUpstream<D : Any, R : FFAResponse<D>>(
2021-07-23 14:10:13 +02:00
val context: Context?,
2021-07-22 14:45:04 +02:00
val behavior: Behavior,
private val url: String,
2021-07-30 10:57:49 +02:00
private val type: Type,
private val oAuth: OAuth
2021-07-22 14:45:04 +02:00
) : Upstream<D> {
2021-07-23 14:10:13 +02:00
2019-08-19 16:50:33 +02:00
enum class Behavior {
2021-07-23 14:10:13 +02:00
Single,
AtOnce,
Progressive
2019-08-19 16:50:33 +02:00
}
2021-06-29 09:37:36 +02:00
override fun fetch(size: Int): Flow<Repository.Response<D>> = flow<Repository.Response<D>> {
2021-07-23 14:10:13 +02:00
context?.let {
if (behavior == Behavior.Single && size != 0) return@flow
val page = ceil(size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1
val url = Uri.parse(url)
.buildUpon()
.appendQueryParameter("page_size", AppContext.PAGE_SIZE.toString())
.appendQueryParameter("page", page.toString())
.appendQueryParameter("scope", Settings.getScopes().joinToString(" "))
.build()
.toString()
2021-07-23 14:10:13 +02:00
get(it, url).fold(
{ response ->
val data = response.getData()
when (behavior) {
Behavior.Single -> emit(networkResponse(data, page, false))
Behavior.Progressive -> emit(networkResponse(data, page, response.next != null))
else -> {
emit(networkResponse(data, page, response.next != null))
if (response.next != null) fetch(size + data.size).collect { emit(it) }
}
}
},
{ error ->
when (error.exception) {
is RefreshError -> EventBus.send(Event.LogOut)
else -> emit(Repository.Response(Repository.Origin.Network, listOf(), page, false))
}
2019-08-19 16:50:33 +02:00
}
2021-07-23 14:10:13 +02:00
)
}
2019-10-31 16:17:37 +01:00
}.flowOn(IO)
2019-08-19 16:50:33 +02:00
2021-07-22 14:45:04 +02:00
private fun networkResponse(data: List<D>, page: Int, hasMore: Boolean) = Repository.Response(
Repository.Origin.Network,
data,
page,
hasMore
)
class GenericDeserializer<T : FFAResponse<*>>(val type: Type) : ResponseDeserializable<T> {
2019-08-19 16:50:33 +02:00
override fun deserialize(reader: Reader): T? {
return Gson().fromJson(reader, type)
}
}
2021-07-23 14:10:13 +02:00
suspend fun get(context: Context, url: String): Result<R, FuelError> {
Log.i("HttpUpstream", "get() - url: $url")
return try {
val normalizedUrl = mustNormalizeUrl(url)
val request = Fuel.get(normalizedUrl).apply {
authorize(context, oAuth)
}
val (_, response, result) = request.awaitObjectResponseResult(GenericDeserializer<R>(type))
if (response.statusCode == 401) {
return retryGet(normalizedUrl)
}
result
} catch (e: Exception) {
Result.error(FuelError.wrap(e))
}
2019-08-19 16:50:33 +02:00
}
private suspend fun retryGet(url: String): Result<R, FuelError> {
Log.i("HttpUpstream", "retryGet() - url: $url")
context?.let {
return try {
oAuth.refreshAccessToken(context)
val request = Fuel.get(url).apply {
authorize(context, oAuth)
}
val (_, _, result) = request.awaitObjectResponseResult(GenericDeserializer<R>(type))
result
} catch (e: Exception) {
Result.error(FuelError.wrap(e))
}
}
throw IllegalStateException("Illegal state: context is null")
}
2021-07-02 13:55:49 +02:00
}