2021-07-12 10:14:26 +02:00
|
|
|
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
|
2021-08-22 09:12:57 +02:00
|
|
|
import android.util.Log
|
2021-08-22 09:48:33 +02:00
|
|
|
import audio.funkwhale.ffa.model.FFAResponse
|
2021-07-12 10:14:26 +02:00
|
|
|
import audio.funkwhale.ffa.utils.*
|
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
|
2019-11-01 13:42:15 +01:00
|
|
|
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
|
|
|
|
|
2021-08-22 09:48:33 +02:00
|
|
|
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
|
|
|
|
|
2021-08-22 09:12:57 +02:00
|
|
|
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))
|
2020-07-08 22:11:50 +02:00
|
|
|
}
|
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
|
|
|
|
)
|
|
|
|
|
2021-08-22 09:48:33 +02:00
|
|
|
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> {
|
2021-08-22 09:12:57 +02:00
|
|
|
Log.i("HttpUpstream", "get() - url: $url")
|
2020-06-21 14:16:30 +02:00
|
|
|
return try {
|
2021-08-22 09:12:57 +02:00
|
|
|
val normalizedUrl = mustNormalizeUrl(url)
|
|
|
|
val request = Fuel.get(normalizedUrl).apply {
|
2021-08-09 06:50:46 +02:00
|
|
|
authorize(context, oAuth)
|
2019-11-25 23:14:16 +01:00
|
|
|
}
|
2021-08-20 11:50:32 +02:00
|
|
|
val (_, response, result) = request.awaitObjectResponseResult(GenericDeserializer<R>(type))
|
|
|
|
if (response.statusCode == 401) {
|
2021-08-22 09:12:57 +02:00
|
|
|
return retryGet(normalizedUrl)
|
2021-08-20 11:50:32 +02:00
|
|
|
}
|
2020-06-21 14:16:30 +02:00
|
|
|
result
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Result.error(FuelError.wrap(e))
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
2021-08-20 11:50:32 +02:00
|
|
|
|
|
|
|
private suspend fun retryGet(url: String): Result<R, FuelError> {
|
2021-08-22 09:12:57 +02:00
|
|
|
Log.i("HttpUpstream", "retryGet() - url: $url")
|
2021-08-20 11:50:32 +02:00
|
|
|
context?.let {
|
|
|
|
return try {
|
2021-08-22 09:12:57 +02:00
|
|
|
oAuth.refreshAccessToken(context)
|
|
|
|
val request = Fuel.get(url).apply {
|
|
|
|
authorize(context, oAuth)
|
2021-08-20 11:50:32 +02:00
|
|
|
}
|
2021-08-22 09:12:57 +02:00
|
|
|
val (_, _, result) = request.awaitObjectResponseResult(GenericDeserializer<R>(type))
|
|
|
|
result
|
2021-08-20 11:50:32 +02:00
|
|
|
} catch (e: Exception) {
|
|
|
|
Result.error(FuelError.wrap(e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw IllegalStateException("Illegal state: context is null")
|
|
|
|
}
|
2021-07-02 13:55:49 +02:00
|
|
|
}
|