package world.respect.datalayer.ext import com.ustadmobile.ihttp.headers.asIHttpHeaders import io.github.aakira.napier.Napier import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.get import io.ktor.client.statement.request import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.http.Url import io.ktor.http.etag import io.ktor.util.reflect.TypeInfo import io.ktor.util.reflect.typeInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import world.respect.lib.dataloadstate.DataErrorResult import world.respect.lib.dataloadstate.DataLoadMetaInfo import world.respect.lib.dataloadstate.DataLoadParams import world.respect.lib.dataloadstate.DataReadyState import world.respect.lib.dataloadstate.DataLoadState import world.respect.lib.dataloadstate.DataLoadingState import world.respect.lib.dataloadstate.NoDataLoadedState import world.respect.datalayer.networkvalidation.BaseDataSourceValidationHelper import world.respect.datalayer.networkvalidation.ExtendedDataSourceValidationHelper suspend fun HttpClient.getAsDataLoadState( url: Url, typeInfo: TypeInfo, validationHelper: BaseDataSourceValidationHelper? = null, block: HttpRequestBuilder.() -> Unit = { }, ): DataLoadState { return try { val response = this.get(url) { block() //note the block can change the URL (eg by adding parameters), so get validationInfo //after running block validationHelper?.also { addCacheValidationHeaders(it) } } val extendedValidationHelper = validationHelper as? ExtendedDataSourceValidationHelper val varyHeader = response.headers.getAll(HttpHeaders.Vary) ?.joinToString(separator = ",") val validationInfoKey = extendedValidationHelper?.validationInfoKey( response.request.headers.asIHttpHeaders(), response.headers.getAll(HttpHeaders.Vary)?.joinToString(separator = ",") ) val metaInfo = DataLoadMetaInfo( url = response.request.url, lastModified = response.lastModifiedAsLong(), etag = response.etag(), consistentThrough = response.consistentThrough(), validationInfoKey = validationInfoKey ?: 0, varyHeader = varyHeader, permissionsLastModified = response.permissionsLastModified(), headers = response.headers, ) return if(response.status == HttpStatusCode.NotModified) { NoDataLoadedState.notModified(metaInfo = metaInfo) }else { val data = response.body(typeInfo) DataReadyState( data = data, metaInfo = metaInfo ) } }catch(t: Throwable) { Napier.d("Exception loading $url", t) DataErrorResult( error = t, metaInfo = DataLoadMetaInfo(url = url) ) } } suspend inline fun HttpClient.getAsDataLoadState( url: Url, validationHelper: BaseDataSourceValidationHelper? = null, noinline block: HttpRequestBuilder.() -> Unit = { }, ): DataLoadState { return getAsDataLoadState(url, typeInfo(), validationHelper, block) } inline fun HttpClient.getDataLoadResultAsFlow( url: Url, dataLoadParams: DataLoadParams, validationHelper: BaseDataSourceValidationHelper? = null, noinline block: HttpRequestBuilder.() -> Unit = { }, ): Flow> { return getDataLoadResultAsFlow( urlFn = { url }, dataLoadParams = dataLoadParams, validationHelper = validationHelper, block = block, ) } /** * @param urlFn Data source functions often return a flow, however sometimes figuring out the url * itself requires a suspended function (such as a database query). The function * that returns the flow is not suspended. Accepting a function parameter makes it easier to * shift the suspended operation to get the url into the flow. */ inline fun HttpClient.getDataLoadResultAsFlow( noinline urlFn: suspend () -> Url, @Suppress("unused") dataLoadParams: DataLoadParams, validationHelper: BaseDataSourceValidationHelper? = null, noinline block: HttpRequestBuilder.() -> Unit = { }, ): Flow> { return flow { val urlVal = urlFn() emit(DataLoadingState(metaInfo = DataLoadMetaInfo(url = urlVal))) emit(getAsDataLoadState(urlVal, validationHelper, block = block)) } }