From 8097e8d06dc6a565e597b63a40e3998e346c51dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 16 Oct 2022 00:09:51 +0200 Subject: [PATCH] [API/Usos] Add syncing Courses and Terms. --- .../edziennik/data/api/Errors.kt | 1 + .../data/api/edziennik/usos/DataUsos.kt | 13 +-- .../edziennik/data/api/edziennik/usos/Usos.kt | 5 +- .../data/api/edziennik/usos/UsosFeatures.kt | 15 +++- .../data/api/edziennik/usos/data/UsosApi.kt | 24 ++++-- .../data/api/edziennik/usos/data/UsosData.kt | 14 ++- .../edziennik/usos/data/api/UsosApiCourses.kt | 86 +++++++++++++++++++ .../edziennik/usos/data/api/UsosApiTerms.kt | 65 ++++++++++++++ .../usos/firstlogin/UsosFirstLogin.kt | 2 +- .../edziennik/data/db/entity/Profile.kt | 4 + 10 files changed, 205 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index b4e892e6..8be89c6e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -203,6 +203,7 @@ const val ERROR_PODLASIE_API_DATA_MISSING = 632 const val ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN = 702 const val ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE = 703 const val ERROR_USOS_NO_STUDENT_PROGRAMMES = 704 +const val ERROR_USOS_API_INCOMPLETE_RESPONSE = 705 const val ERROR_TEMPLATE_WEB_OTHER = 801 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt index 0fd1b2ba..5cd054f8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/DataUsos.kt @@ -25,7 +25,7 @@ class DataUsos( } } - override fun generateUserCode() = "$schoolId:${studentNumber ?: studentId}" + override fun generateUserCode() = "$schoolId:${profile?.studentNumber ?: studentId}" var schoolId: String? get() { mSchoolId = mSchoolId ?: loginStore.getLoginData("schoolId", null); return mSchoolId } @@ -67,13 +67,8 @@ class DataUsos( set(value) { loginStore.putLoginData("oauthTokenIsUser", value); mOauthTokenIsUser = value } private var mOauthTokenIsUser: Boolean? = null - var studentId: String? - get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } + var studentId: Int + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } 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 + private var mStudentId: Int? = null } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt index d676d006..cbf92ca8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt @@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosData import pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin.UsosFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLogin import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent @@ -58,9 +59,9 @@ class Usos( d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") UsosLogin(data) { - /*UsosData(data) { + UsosData(data) { completed() - }*/ + } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt index a4ee1389..bdcf9c78 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/UsosFeatures.kt @@ -4,15 +4,26 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.FEATURE_ALWAYS_NEEDED import pl.szczodrzynski.edziennik.data.api.FEATURE_STUDENT_INFO -import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_USOS_API -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_USOS +import pl.szczodrzynski.edziennik.data.api.FEATURE_TEAM_INFO import pl.szczodrzynski.edziennik.data.api.models.Feature const val ENDPOINT_USOS_API_USER = 7000 +const val ENDPOINT_USOS_API_TERMS = 7010 +const val ENDPOINT_USOS_API_COURSES = 7020 val UsosFeatures = listOf( Feature(LOGIN_TYPE_USOS, FEATURE_STUDENT_INFO, listOf( ENDPOINT_USOS_API_USER to LOGIN_METHOD_USOS_API, ), listOf(LOGIN_METHOD_USOS_API)), + + Feature(LOGIN_TYPE_USOS, FEATURE_SCHOOL_INFO, listOf( + ENDPOINT_USOS_API_TERMS to LOGIN_METHOD_USOS_API, + ), listOf(LOGIN_METHOD_USOS_API)), + + Feature(LOGIN_TYPE_USOS, FEATURE_TEAM_INFO, listOf( + ENDPOINT_USOS_API_COURSES to LOGIN_METHOD_USOS_API, + ), listOf(LOGIN_METHOD_USOS_API)), ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt index 1d3ab69c..829fefa4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosApi.kt @@ -6,7 +6,6 @@ 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 @@ -17,10 +16,7 @@ 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.ext.* import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.* import java.util.UUID @@ -42,6 +38,9 @@ open class UsosApi(open val data: DataUsos, open val lastSync: Long?) { val profile get() = data.profile + protected fun JsonObject.getLangString(key: String) = + this.getJsonObject(key)?.getString("pl") + private fun valueToString(value: Any) = when (value) { is String -> value is Number -> value.toString() @@ -74,15 +73,22 @@ open class UsosApi(open val data: DataUsos, open val lastSync: Long?) { fun apiRequest( tag: String, service: String, - params: Map, + params: Map? = null, + fields: List? = null, responseType: ResponseType, onSuccess: (data: T, response: Response?) -> Unit, ) { val url = "${data.instanceUrl}services/$service" d(tag, "Request: Usos/Api - $url") - val formData = params.mapValues { - valueToString(it.value) - } + + val formData = mutableMapOf() + if (params != null) + formData.putAll(params.mapValues { + valueToString(it.value) + }) + if (fields != null) + formData["fields"] = valueToString(fields) + val auth = mutableMapOf( "realm" to url, "oauth_consumer_key" to (data.oauthConsumerKey ?: ""), diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt index 5ad199ff..5ea6ab66 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/UsosData.kt @@ -7,7 +7,11 @@ 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_COURSES +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_USER +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms import pl.szczodrzynski.edziennik.utils.Utils.d class UsosData(val data: DataUsos, val onSuccess: () -> Unit) { @@ -39,9 +43,17 @@ class UsosData(val data: DataUsos, val onSuccess: () -> Unit) { 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 -> { + /*ENDPOINT_USOS_API_USER -> { data.startProgress(R.string.edziennik_progress_endpoint_student_info) // TemplateWebSample(data, lastSync, onSuccess) + }*/ + ENDPOINT_USOS_API_TERMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_school_info) + UsosApiTerms(data, lastSync, onSuccess) + } + ENDPOINT_USOS_API_COURSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teams) + UsosApiCourses(data, lastSync, onSuccess) } else -> onSuccess(endpointId) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt new file mode 100644 index 00000000..8f7359b5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiCourses.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kuba SzczodrzyƄski 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.ext.* + +class UsosApiCourses( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiCourses" + } + + init { + apiRequest( + tag = TAG, + service = "courses/user", + fields = listOf( + // "terms" to listOf("id", "name", "start_date", "end_date"), + "course_editions" to listOf( + "course_id", + // "course_name", + // "term_id", + "user_groups" to listOf( + "course_unit_id", + "group_number", + "class_type", + "class_type_id", + // "lecturers", + ), + ), + ), + responseType = ResponseType.OBJECT, + ) { json, response -> + if (!processResponse(json)) { + data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response) + return@apiRequest + } + + data.setSyncNext(ENDPOINT_USOS_API_COURSES, 2 * DAY) + onSuccess(ENDPOINT_USOS_API_COURSES) + } + } + + private fun processResponse(json: JsonObject): Boolean { + // val term = json.getJsonArray("terms")?.firstOrNull() ?: return false + val courseEditions = json.getJsonObject("course_editions") + ?.entrySet() + ?.flatMap { it.value.asJsonArray } + ?.map { it.asJsonObject } ?: return false + + var hasValidTeam = false + for (courseEdition in courseEditions) { + val courseId = courseEdition.getString("course_id") ?: continue + // val courseName = courseEdition.getLangString("course_name") ?: continue + val userGroups = courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue + for (userGroup in userGroups) { + val courseUnitId = userGroup.getLong("course_unit_id") ?: continue + val groupNumber = userGroup.getInt("group_number") ?: continue + val classType = userGroup.getLangString("class_type") ?: continue + val classTypeId = userGroup.getString("class_type_id") ?: continue + + data.teamList.put(courseUnitId, Team( + profileId, + courseUnitId, + "$classType $groupNumber ($courseId)", + 2, + "${data.schoolId}:${courseId} $classTypeId$groupNumber", + -1, + )) + hasValidTeam = true + } + } + return hasValidTeam + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt new file mode 100644 index 00000000..4a17bffa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/data/api/UsosApiTerms.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) Kuba SzczodrzyƄski 2022-10-15. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS +import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.ext.* +import pl.szczodrzynski.edziennik.utils.models.Date + +class UsosApiTerms( + override val data: DataUsos, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit, +) : UsosApi(data, lastSync) { + companion object { + const val TAG = "UsosApiTerms" + } + + init { + apiRequest( + tag = TAG, + service = "terms/search", + params = mapOf( + "query" to Date.getToday().year.toString(), + ), + responseType = ResponseType.ARRAY, + ) { json, response -> + if (!processResponse(json)) { + data.error(UsosApiCourses.TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response) + return@apiRequest + } + + data.setSyncNext(ENDPOINT_USOS_API_TERMS, 7 * DAY) + onSuccess(ENDPOINT_USOS_API_TERMS) + } + } + + private fun processResponse(json: JsonArray): Boolean { + val dates = mutableSetOf() + for (term in json.asJsonObjectList()) { + if (!term.getBoolean("is_active", false)) + continue + val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) } + val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) } + if (startDate != null) + dates += startDate + if (finishDate != null) + dates += finishDate + } + val datesSorted = dates.sorted() + if (datesSorted.size != 3) + return false + profile?.studentSchoolYearStart = datesSorted[0].year + profile?.dateSemester1Start = datesSorted[0] + profile?.dateSemester2Start = datesSorted[1] + profile?.dateYearEnd = datesSorted[2] + return true + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt index ad51cf92..4cb38cc6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/firstlogin/UsosFirstLogin.kt @@ -68,9 +68,9 @@ class UsosFirstLogin(val data: DataUsos, val onSuccess: () -> Unit) { accountName = null, // student account studentData = JsonObject( "studentId" to json.getInt("id"), - "studentNumber" to json.getInt("student_number"), ), ).also { + it.studentNumber = json.getInt("student_number", -1) it.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id") } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt index 9fc0521a..b1615fbc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt @@ -233,6 +233,10 @@ open class Profile( MainActivity.DRAWER_ITEM_GRADES, MainActivity.DRAWER_ITEM_HOMEWORK ) + LOGIN_TYPE_USOS -> listOf( + MainActivity.DRAWER_ITEM_TIMETABLE, + MainActivity.DRAWER_ITEM_AGENDA + ) else -> listOf( MainActivity.DRAWER_ITEM_TIMETABLE, MainActivity.DRAWER_ITEM_AGENDA,