forked from github/szkolny
[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"
|
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?
|
var oauthTokenKey: String?
|
||||||
get() { mOauthTokenKey = mOauthTokenKey ?: loginStore.getLoginData("oauthTokenKey", null); return mOauthTokenKey }
|
get() { mOauthTokenKey = mOauthTokenKey ?: loginStore.getLoginData("oauthTokenKey", null); return mOauthTokenKey }
|
||||||
set(value) { loginStore.putLoginData("oauthTokenKey", value); mOauthTokenKey = value }
|
set(value) { loginStore.putLoginData("oauthTokenKey", value); mOauthTokenKey = value }
|
||||||
@ -36,4 +51,14 @@ class DataUsos(
|
|||||||
get() { mOauthTokenSecret = mOauthTokenSecret ?: loginStore.getLoginData("oauthTokenSecret", null); return mOauthTokenSecret }
|
get() { mOauthTokenSecret = mOauthTokenSecret ?: loginStore.getLoginData("oauthTokenSecret", null); return mOauthTokenSecret }
|
||||||
set(value) { loginStore.putLoginData("oauthTokenSecret", value); mOauthTokenSecret = value }
|
set(value) { loginStore.putLoginData("oauthTokenSecret", value); mOauthTokenSecret = value }
|
||||||
private var mOauthTokenSecret: String? = null
|
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.PluralsRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
fun CharSequence?.isNotNullNorEmpty(): Boolean {
|
fun CharSequence?.isNotNullNorEmpty(): Boolean {
|
||||||
return this != null && this.isNotEmpty()
|
return this != null && this.isNotEmpty()
|
||||||
@ -343,3 +344,10 @@ fun Int.toStringHolder() = StringHolder(this)
|
|||||||
fun CharSequence.toStringHolder() = StringHolder(this)
|
fun CharSequence.toStringHolder() = StringHolder(this)
|
||||||
|
|
||||||
fun @receiver:StringRes Int.resolveString(context: Context) = context.getString(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(
|
private fun executeLibrus(
|
||||||
activity: AppCompatActivity,
|
activity: AppCompatActivity,
|
||||||
profileId: Int?,
|
profileId: Int?,
|
||||||
onSuccess: ((params: Bundle) -> Unit)? = null,
|
onSuccess: ((params: Bundle) -> Unit)?,
|
||||||
onFailure: (() -> Unit)? = null,
|
onFailure: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
if (profileId == null)
|
if (profileId == null)
|
||||||
return
|
return
|
||||||
@ -125,4 +125,16 @@ class UserActionManager(val app: App) {
|
|||||||
onFailure = onFailure
|
onFailure = onFailure
|
||||||
).show()
|
).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