mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2024-11-24 10:54:36 -06:00
[API/Usos] Add base rest API class.
This commit is contained in:
parent
7935d0f097
commit
c7362bce12
@ -27,6 +27,21 @@ class DataUsos(
|
||||
|
||||
override fun generateUserCode() = "USOS:TEST"
|
||||
|
||||
var instanceUrl: String?
|
||||
get() { mInstanceUrl = mInstanceUrl ?: loginStore.getLoginData("instanceUrl", null); return mInstanceUrl }
|
||||
set(value) { loginStore.putLoginData("instanceUrl", value); mInstanceUrl = value }
|
||||
private var mInstanceUrl: String? = null
|
||||
|
||||
var oauthConsumerKey: String?
|
||||
get() { mOauthConsumerKey = mOauthConsumerKey ?: loginStore.getLoginData("oauthConsumerKey", null); return mOauthConsumerKey }
|
||||
set(value) { loginStore.putLoginData("oauthConsumerKey", value); mOauthConsumerKey = value }
|
||||
private var mOauthConsumerKey: String? = null
|
||||
|
||||
var oauthConsumerSecret: String?
|
||||
get() { mOauthConsumerSecret = mOauthConsumerSecret ?: loginStore.getLoginData("oauthConsumerSecret", null); return mOauthConsumerSecret }
|
||||
set(value) { loginStore.putLoginData("oauthConsumerSecret", value); mOauthConsumerSecret = value }
|
||||
private var mOauthConsumerSecret: String? = null
|
||||
|
||||
var oauthTokenKey: String?
|
||||
get() { mOauthTokenKey = mOauthTokenKey ?: loginStore.getLoginData("oauthTokenKey", null); return mOauthTokenKey }
|
||||
set(value) { loginStore.putLoginData("oauthTokenKey", value); mOauthTokenKey = value }
|
||||
@ -36,4 +51,14 @@ class DataUsos(
|
||||
get() { mOauthTokenSecret = mOauthTokenSecret ?: loginStore.getLoginData("oauthTokenSecret", null); return mOauthTokenSecret }
|
||||
set(value) { loginStore.putLoginData("oauthTokenSecret", value); mOauthTokenSecret = value }
|
||||
private var mOauthTokenSecret: String? = null
|
||||
|
||||
var studentId: String?
|
||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
|
||||
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
|
||||
private var mStudentId: String? = null
|
||||
|
||||
var studentNumber: String?
|
||||
get() { mStudentNumber = mStudentNumber ?: profile?.getStudentData("studentNumber", null); return mStudentNumber }
|
||||
set(value) { profile?.putStudentData("studentNumber", value) ?: return; mStudentNumber = value }
|
||||
private var mStudentNumber: String? = null
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2022-10-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.AbsCallbackHandler
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.JsonArrayCallbackHandler
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
|
||||
import pl.szczodrzynski.edziennik.data.api.SERVER_USER_AGENT
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
||||
import pl.szczodrzynski.edziennik.ext.hmacSHA1
|
||||
import pl.szczodrzynski.edziennik.ext.toQueryString
|
||||
import pl.szczodrzynski.edziennik.ext.urlEncode
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection.*
|
||||
import java.util.UUID
|
||||
|
||||
open class UsosApi(open val data: DataUsos, open val lastSync: Long?) {
|
||||
companion object {
|
||||
private const val TAG = "UsosApi"
|
||||
}
|
||||
|
||||
enum class ResponseType {
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
PLAIN,
|
||||
}
|
||||
|
||||
val profileId
|
||||
get() = data.profile?.id ?: -1
|
||||
|
||||
val profile
|
||||
get() = data.profile
|
||||
|
||||
private fun valueToString(value: Any) = when (value) {
|
||||
is String -> value
|
||||
is Number -> value.toString()
|
||||
is List<*> -> listToString(value)
|
||||
else -> value.toString()
|
||||
}
|
||||
|
||||
private fun listToString(list: List<*>): String {
|
||||
return list.map {
|
||||
if (it is Pair<*, *> && it.first is String && it.second is List<*>)
|
||||
return@map "${it.first}[${listToString(it.second as List<*>)}]"
|
||||
return@map valueToString(it ?: "")
|
||||
}.joinToString("|")
|
||||
}
|
||||
|
||||
private fun buildSignature(method: String, url: String, params: Map<String, String>): String {
|
||||
val query = params.toQueryString()
|
||||
val signatureString = listOf(
|
||||
method.uppercase(),
|
||||
url.urlEncode(),
|
||||
query.urlEncode(),
|
||||
).joinToString("&")
|
||||
val signingKey = listOf(
|
||||
data.oauthConsumerSecret ?: "",
|
||||
data.oauthTokenSecret ?: "",
|
||||
).joinToString("&") { it.urlEncode() }
|
||||
return signatureString.hmacSHA1(signingKey)
|
||||
}
|
||||
|
||||
fun <T> apiRequest(
|
||||
tag: String,
|
||||
service: String,
|
||||
params: Map<String, Any>,
|
||||
responseType: ResponseType,
|
||||
onSuccess: (data: T) -> Unit,
|
||||
) {
|
||||
val url = "${data.instanceUrl}/services/$service"
|
||||
d(tag, "Request: Usos/Api - $url")
|
||||
val formData = params.mapValues {
|
||||
valueToString(it.value)
|
||||
}
|
||||
val auth = mutableMapOf(
|
||||
"realm" to url,
|
||||
"oauth_consumer_key" to (data.oauthConsumerKey ?: ""),
|
||||
"oauth_nonce" to UUID.randomUUID().toString(),
|
||||
"oauth_signature_method" to "HMAC-SHA1",
|
||||
"oauth_timestamp" to currentTimeUnix().toString(),
|
||||
"oauth_token" to (data.oauthTokenKey ?: ""),
|
||||
"oauth_version" to "1.0",
|
||||
)
|
||||
val signature = buildSignature("POST", url, formData + auth)
|
||||
auth["oauth_signature"] = signature
|
||||
|
||||
val authString = auth.map {
|
||||
"""${it.key}="${it.value.urlEncode()}""""
|
||||
}.joinToString(", ")
|
||||
|
||||
Request.builder()
|
||||
.url(url)
|
||||
.userAgent(SERVER_USER_AGENT)
|
||||
.addHeader("Authorization", "OAuth $authString")
|
||||
.post()
|
||||
.setTextBody(formData.toQueryString(), MediaTypeUtils.APPLICATION_FORM)
|
||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HTTP_UNAUTHORIZED)
|
||||
.allowErrorCode(HTTP_FORBIDDEN)
|
||||
.allowErrorCode(HTTP_NOT_FOUND)
|
||||
.allowErrorCode(HTTP_UNAVAILABLE)
|
||||
.callback(getCallback(tag, responseType, onSuccess))
|
||||
.build()
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> getCallback(
|
||||
tag: String,
|
||||
responseType: ResponseType,
|
||||
onSuccess: (data: T) -> Unit,
|
||||
) = when (responseType) {
|
||||
ResponseType.OBJECT -> object : JsonCallbackHandler() {
|
||||
override fun onSuccess(data: JsonObject?, response: Response?) {
|
||||
processResponse(response, data as T, onSuccess)
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
processError(tag, response, throwable)
|
||||
}
|
||||
}
|
||||
ResponseType.ARRAY -> object : JsonArrayCallbackHandler() {
|
||||
override fun onSuccess(data: JsonArray?, response: Response?) {
|
||||
processResponse(response, data as T, onSuccess)
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
processError(tag, response, throwable)
|
||||
}
|
||||
}
|
||||
ResponseType.PLAIN -> object : TextCallbackHandler() {
|
||||
override fun onSuccess(data: String?, response: Response?) {
|
||||
processResponse(response, data as T, onSuccess)
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
processError(tag, response, throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> processResponse(
|
||||
response: Response?,
|
||||
data: T?,
|
||||
onSuccess: (data: T) -> Unit,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private fun processError(
|
||||
tag: String,
|
||||
response: Response?,
|
||||
throwable: Throwable?,
|
||||
) {
|
||||
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable))
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2022-10-13.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data
|
||||
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_USER
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
|
||||
class UsosData(val data: DataUsos, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
private const val TAG = "UsosData"
|
||||
}
|
||||
|
||||
init {
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
|
||||
private fun nextEndpoint(onSuccess: () -> Unit) {
|
||||
if (data.targetEndpointIds.isEmpty()) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
if (data.cancelled) {
|
||||
onSuccess()
|
||||
return
|
||||
}
|
||||
val id = data.targetEndpointIds.firstKey()
|
||||
val lastSync = data.targetEndpointIds.remove(id)
|
||||
useEndpoint(id, lastSync) { endpointId ->
|
||||
data.progress(data.progressStep)
|
||||
nextEndpoint(onSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
|
||||
d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
|
||||
when (endpointId) {
|
||||
ENDPOINT_USOS_API_USER -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
|
||||
TemplateWebSample(data, lastSync, onSuccess)
|
||||
}
|
||||
else -> onSuccess(endpointId)
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import android.text.style.StyleSpan
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import java.net.URLEncoder
|
||||
|
||||
fun CharSequence?.isNotNullNorEmpty(): Boolean {
|
||||
return this != null && this.isNotEmpty()
|
||||
@ -343,3 +344,10 @@ fun Int.toStringHolder() = StringHolder(this)
|
||||
fun CharSequence.toStringHolder() = StringHolder(this)
|
||||
|
||||
fun @receiver:StringRes Int.resolveString(context: Context) = context.getString(this)
|
||||
|
||||
fun String.urlEncode(): String = URLEncoder.encode(this, "UTF-8").replace("+", "%20")
|
||||
|
||||
fun Map<String, String>.toQueryString() = this
|
||||
.map { it.key.urlEncode() to it.value.urlEncode() }
|
||||
.sortedBy { it.first }
|
||||
.joinToString("&") { "${it.first}=${it.second}" }
|
||||
|
@ -99,8 +99,8 @@ class UserActionManager(val app: App) {
|
||||
private fun executeLibrus(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
onSuccess: ((params: Bundle) -> Unit)? = null,
|
||||
onFailure: (() -> Unit)? = null,
|
||||
onSuccess: ((params: Bundle) -> Unit)?,
|
||||
onFailure: (() -> Unit)?,
|
||||
) {
|
||||
if (profileId == null)
|
||||
return
|
||||
@ -125,4 +125,16 @@ class UserActionManager(val app: App) {
|
||||
onFailure = onFailure
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun executeOauth(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
params: Bundle?,
|
||||
onSuccess: ((params: Bundle) -> Unit)?,
|
||||
onFailure: (() -> Unit)?,
|
||||
) {
|
||||
if (profileId == null || params == null)
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user