package audio.funkwhale.ffa.utils import android.content.Context import android.os.Build import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import audio.funkwhale.ffa.model.DownloadInfo import audio.funkwhale.ffa.repositories.Repository import com.github.kittinunf.fuel.core.FuelError import com.github.kittinunf.fuel.core.Request import com.google.android.exoplayer2.offline.Download import com.google.gson.Gson import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import net.openid.appauth.ClientSecretPost import java.text.SimpleDateFormat import java.util.Date import kotlin.coroutines.CoroutineContext inline fun Flow>.untilNetwork( scope: CoroutineScope, context: CoroutineContext = Main, crossinline callback: (data: List, isCache: Boolean, page: Int, hasMore: Boolean) -> Unit ) { scope.launch(context) { collect { data -> callback(data.data, data.origin == Repository.Origin.Cache, data.page, data.hasMore) } } } fun Int.onApi(block: () -> T) { if (Build.VERSION.SDK_INT >= this) { block() } } fun Int.onApi(block: () -> T, elseBlock: (() -> U)) { if (Build.VERSION.SDK_INT >= this) { block() } else { elseBlock() } } fun Request.authorize(context: Context, oAuth: OAuth): Request { return runBlocking { this@authorize.apply { if (!Settings.isAnonymous()) { oAuth.state().let { state -> state.accessTokenExpirationTime?.let { Log.i("Request.authorize()", "Accesstoken expiration: ${Date(it).format()}") } val old = state.accessToken val auth = ClientSecretPost(oAuth.state().clientSecret) val done = CompletableDeferred() val tokenService = oAuth.service(context) state.performActionWithFreshTokens(tokenService, auth) { token, _, e -> if (e != null) { Log.e("Request.authorize()", "performActionWithFreshToken failed: $e") Log.e("Request.authorize()", Log.getStackTraceString(e)) } if (token == old) { Log.i("Request.authorize()", "Accesstoken not renewed") } if (token != old && token != null) { state.save() } header("Authorization", "Bearer ${oAuth.state().accessToken}") done.complete(true) } done.await() tokenService.dispose() return@runBlocking this } } } } } fun FuelError.formatResponseMessage(): String { return "${response.statusCode}: ${response.url}" } fun Download.getMetadata(): DownloadInfo? = Gson().fromJson(String(this.request.data), DownloadInfo::class.java) val ISO_8601_DATE_TIME_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") fun Date.format(): String { return ISO_8601_DATE_TIME_FORMAT.format(this) } fun String?.containsIgnoringCase(candidate: String): Boolean = this != null && this.lowercase().contains(candidate.lowercase()) inline fun LiveData.mergeWith( u: LiveData, v: LiveData, crossinline block: (valT: T, valU: U, valV: V) -> R ): LiveData = MediatorLiveData().apply { addSource(this@mergeWith) { if (u.value != null && v.value != null) { postValue(block(it, u.value!!, v.value!!)) } } addSource(u) { if (this@mergeWith.value != null && u.value != null) { postValue(block(this@mergeWith.value!!, it, v.value!!)) } } addSource(v) { if (this@mergeWith.value != null && u.value != null) { postValue(block(this@mergeWith.value!!, u.value!!, it)) } } } inline fun LiveData.mergeWith( u: LiveData, v: LiveData, w: LiveData, crossinline block: (valT: T, valU: U, valV: V, valW: W) -> R ): LiveData = MediatorLiveData().apply { addSource(this@mergeWith) { if (u.value != null && v.value != null && w.value != null) { postValue(block(it, u.value!!, v.value!!, w.value!!)) } } addSource(u) { if (this@mergeWith.value != null && v.value != null && w.value != null) { postValue(block(this@mergeWith.value!!, it, v.value!!, w.value!!)) } } addSource(v) { if (this@mergeWith.value != null && u.value != null && w.value != null) { postValue(block(this@mergeWith.value!!, u.value!!, it, w.value!!)) } } addSource(w) { if (this@mergeWith.value != null && u.value != null && v.value != null) { postValue(block(this@mergeWith.value!!, u.value!!, v.value!!, it)) } } } public fun String?.toIntOrElse(default: Int): Int = this?.toIntOrNull(radix = 10) ?: default