From 535d6088296b3441743d57f9c5eb7349150c8deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 5 Oct 2019 14:26:01 +0200 Subject: [PATCH] [APIv2] Add e-register Template --- .../edziennik/api/v2/ApiService.kt | 2 + .../edziennik/api/v2/Endpoints.kt | 31 ++-- .../szczodrzynski/edziennik/api/v2/Errors.kt | 2 + .../edziennik/api/v2/LoginMethods.kt | 16 ++ .../edziennik/api/v2/template/Template.kt | 173 ++++++++++++++++++ .../api/v2/template/data/DataTemplate.kt | 76 ++++++++ .../api/v2/template/data/TemplateApi.kt | 44 +++++ .../api/v2/template/data/TemplateData.kt | 59 ++++++ .../api/v2/template/data/TemplateWeb.kt | 47 +++++ .../v2/template/data/api/TemplateApiSample.kt | 41 +++++ .../v2/template/data/web/TemplateWebSample.kt | 40 ++++ .../template/data/web/TemplateWebSample2.kt | 40 ++++ .../api/v2/template/login/TemplateLogin.kt | 61 ++++++ .../api/v2/template/login/TemplateLoginApi.kt | 47 +++++ .../api/v2/template/login/TemplateLoginWeb.kt | 55 ++++++ app/src/main/res/values/strings.xml | 2 + 16 files changed, 720 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/Template.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/DataTemplate.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateApi.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateWeb.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/api/TemplateApiSample.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample2.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLogin.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginApi.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginWeb.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt index 0ce24631..231aa893 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt @@ -20,6 +20,7 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.librus.Librus import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiTask +import pl.szczodrzynski.edziennik.api.v2.template.Template import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile import kotlin.math.min @@ -150,6 +151,7 @@ class ApiService : Service() { edziennikInterface = when (loginStore.type) { LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) + LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) else -> null } if (edziennikInterface == null) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Endpoints.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Endpoints.kt index b59111ee..f43db6bb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Endpoints.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Endpoints.kt @@ -48,6 +48,10 @@ const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 3040 const val ENDPOINT_LIBRUS_MESSAGES_GET = 3040 +const val ENDPOINT_TEMPLATE_WEB_SAMPLE = 9991 +const val ENDPOINT_TEMPLATE_WEB_SAMPLE_2 = 9992 +const val ENDPOINT_TEMPLATE_API_SAMPLE = 9993 + val endpoints = listOf( // LIBRUS: API @@ -141,19 +145,14 @@ val endpoints = listOf( ), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)) ) -/* - SYNC: - - look up all endpoints for the given API and given features - - load "next sync timers" for every endpoint - - exclude every endpoint which does not need to sync now - - check all needed login methods - create a login method list, using methods' dependencies as well - use all login methods, saving completed logins to data store - - instantiate all endpoint classes and sync them (writing to data store, returns onSuccess or error Callback) - - */ +val templateEndpoints = listOf( + Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_TEMPLATE_WEB_SAMPLE to LOGIN_METHOD_TEMPLATE_WEB + ), listOf(LOGIN_METHOD_TEMPLATE_WEB)), + Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_SCHOOL_INFO, listOf( + ENDPOINT_TEMPLATE_WEB_SAMPLE_2 to LOGIN_METHOD_TEMPLATE_WEB + ), listOf(LOGIN_METHOD_TEMPLATE_WEB)), + Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( + ENDPOINT_TEMPLATE_API_SAMPLE to LOGIN_METHOD_TEMPLATE_API + ), listOf(LOGIN_METHOD_TEMPLATE_API)) +) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt index f352e2a9..561f8906 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt @@ -93,6 +93,8 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT = 169 const val ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT = 170 const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID = 171 +const val ERROR_TEMPLATE_WEB_OTHER = 301 + const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901 const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902 const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt index 0b700a88..96f65e71 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt @@ -9,6 +9,8 @@ import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod +import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginApi +import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginWeb const val SYNERGIA_API_ENABLED = true @@ -17,6 +19,7 @@ const val LOGIN_TYPE_LIBRUS = 2 const val LOGIN_TYPE_IUCZNIOWIE = 3 const val LOGIN_TYPE_VULCAN = 4 const val LOGIN_TYPE_DEMO = 20 +const val LOGIN_TYPE_TEMPLATE = 21 // LOGIN MODES const val LOGIN_MODE_LIBRUS_EMAIL = 0 @@ -25,6 +28,7 @@ const val LOGIN_MODE_LIBRUS_JST = 2 const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0 const val LOGIN_MODE_IDZIENNIK_WEB = 0 const val LOGIN_MODE_VULCAN_WEB = 0 +const val LOGIN_MODE_TEMPLATE_WEB = 0 // LOGIN METHODS const val LOGIN_METHOD_NOT_NEEDED = -1 @@ -37,6 +41,8 @@ const val LOGIN_METHOD_IDZIENNIK_WEB = 100 const val LOGIN_METHOD_IDZIENNIK_API = 200 const val LOGIN_METHOD_VULCAN_WEB = 100 const val LOGIN_METHOD_VULCAN_API = 200 +const val LOGIN_METHOD_TEMPLATE_WEB = 100 +const val LOGIN_METHOD_TEMPLATE_API = 200 val librusLoginMethods = listOf( LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, LibrusLoginPortal::class.java) @@ -64,4 +70,14 @@ val librusLoginMethods = listOf( .withRequiredLoginMethod { profile, _ -> if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED } +) + +val templateLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB } ) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/Template.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/Template.kt new file mode 100644 index 00000000..93a808e7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/Template.kt @@ -0,0 +1,173 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template + +import android.util.Log +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_NOT_NEEDED +import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.api.v2.models.Endpoint +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.api.v2.template.data.TemplateData +import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLogin +import pl.szczodrzynski.edziennik.api.v2.templateEndpoints +import pl.szczodrzynski.edziennik.api.v2.templateLoginMethods +import pl.szczodrzynski.edziennik.data.db.modules.api.EndpointTimer +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_NEVER +import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.utils.Utils + +class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Template" + } + + val internalErrorList = mutableListOf() + val data: DataTemplate + private var cancelled = false + + init { + data = DataTemplate(app, profile, loginStore).apply { + callback = wrapCallback(this@Template.callback) + } + data.satisfyLoginMethods() + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?) { + val possibleLoginMethods = data.loginMethods.toMutableList() + + for (loginMethod in templateLoginMethods) { + if (loginMethod.isPossible(profile, loginStore)) + possibleLoginMethods += loginMethod.loginMethodId + } + + //var highestLoginMethod = 0 + var endpointList = mutableListOf() + val requiredLoginMethods = mutableListOf() + + data.targetEndpointIds.clear() + data.targetLoginMethodIds.clear() + + // get all endpoints for every feature, only if possible to login + for (featureId in featureIds) { + templateEndpoints.filter { + it.featureId == featureId && possibleLoginMethods.containsAll(it.requiredLoginMethods) + } + .let { + endpointList.addAll(it) + } + } + + val timestamp = System.currentTimeMillis() + + endpointList = endpointList + // sort the endpoint list by feature ID and priority + .sortedWith(compareBy(Endpoint::featureId, Endpoint::priority)) + // select only the most important endpoint for each feature + .distinctBy { it.featureId } + .toMutableList() + // add all endpoint IDs and required login methods, filtering using timers + .onEach { feature -> + feature.endpointIds.forEach { endpoint -> + (data.endpointTimers + .singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id ?: -1, endpoint.first)) + .let { timer -> + if (timer.nextSync == SYNC_ALWAYS || + (timer.viewId == viewId) || + (timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp)) { + data.targetEndpointIds.add(endpoint.first) + requiredLoginMethods.add(endpoint.second) + } + } + } + } + + // check every login method for any dependencies + for (loginMethodId in requiredLoginMethods) { + var requiredLoginMethod: Int? = loginMethodId + while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { + templateLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod -> + if (requiredLoginMethod != null) + data.targetLoginMethodIds.add(requiredLoginMethod!!) + requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore) + } + } + } + + // sort and distinct every login method and endpoint + data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList() + data.targetLoginMethodIds.sort() + + data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList() + data.targetEndpointIds.sort() + + Log.d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") + Log.d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + + TemplateLogin(data) { + TemplateData(data) { + completed() + } + } + } + + override fun getMessage(messageId: Int) { + + } + + override fun cancel() { + Utils.d(TAG, "Cancelled") + cancelled = true + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { + callback.onCompleted() + } + + override fun onProgress(step: Int) { + callback.onProgress(step) + } + + override fun onStartProgress(stringRes: Int) { + callback.onStartProgress(stringRes) + } + + override fun onError(apiError: ApiError) { + when (apiError.errorCode) { + in internalErrorList -> { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + } + CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { + internalErrorList.add(apiError.errorCode) + loginStore.removeLoginData("refreshToken") // force a clean login + //loginLibrus() + } + else -> callback.onError(apiError) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/DataTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/DataTemplate.kt new file mode 100644 index 00000000..36a973f5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/DataTemplate.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data + +import okhttp3.Cookie +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_TEMPLATE_API +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_TEMPLATE_WEB +import pl.szczodrzynski.edziennik.api.v2.models.Data +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.isNotNullNorEmpty + +/** + * Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art + * + * Use https://codepen.io/kubasz/pen/RwwwbGN to easily generate the student data getters/setters + */ +class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = webExpiryTime-30 > currentTimeUnix() && webCookie.isNotNullNorEmpty() + fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiToken.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_TEMPLATE_WEB + app.cookieJar.saveFromResponse(null, listOf( + Cookie.Builder() + .name("AuthCookie") + .value(webCookie!!) + .domain("eregister.example.com") + .secure().httpOnly().build() + )) + } + if (isApiLoginValid()) + loginMethods += LOGIN_METHOD_TEMPLATE_API + } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebCookie: String? = null + var webCookie: String? + get() { mWebCookie = mWebCookie ?: profile?.getStudentData("webCookie", null); return mWebCookie } + set(value) { profile?.putStudentData("webCookie", value) ?: return; mWebCookie = value } + + private var mWebExpiryTime: Long? = null + var webExpiryTime: Long + get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L } + set(value) { profile?.putStudentData("webExpiryTime", value) ?: return; mWebExpiryTime = value } + + /* _ + /\ (_) + / \ _ __ _ + / /\ \ | '_ \| | + / ____ \| |_) | | + /_/ \_\ .__/|_| + | | + |*/ + private var mApiToken: String? = null + var apiToken: String? + get() { mApiToken = mApiToken ?: profile?.getStudentData("apiToken", null); return mApiToken } + set(value) { profile?.putStudentData("apiToken", value) ?: return; mApiToken = value } + + private var mApiExpiryTime: Long? = null + var apiExpiryTime: Long + get() { mApiExpiryTime = mApiExpiryTime ?: profile?.getStudentData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L } + set(value) { profile?.putStudentData("apiExpiryTime", value) ?: return; mApiExpiryTime = value } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateApi.kt new file mode 100644 index 00000000..cff0c42a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateApi.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.api.v2.ERROR_TEMPLATE_WEB_OTHER +import pl.szczodrzynski.edziennik.api.v2.GET +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.currentTimeUnix + +open class TemplateApi(open val data: DataTemplate) { + companion object { + private const val TAG = "TemplateApi" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + /** + * This will be used by all TemplateApi* endpoints. + * + * You can customize this method's parameters to best fit the implemented e-register. + * Just make sure that [tag] and [onSuccess] is present. + */ + fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject?) -> Unit) { + val json = JsonObject() + json.addProperty("foo", "bar") + json.addProperty("sample", "text") + + if (currentTimeUnix() % 4L == 0L) { + // let's set a 20% chance of error, just as a test + data.error(ApiError(tag, ERROR_TEMPLATE_WEB_OTHER) + .withApiResponse("404 Not Found - this is the text returned by the API")) + return + } + + onSuccess(json) + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt new file mode 100644 index 00000000..16601d2c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateData.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_API_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE_2 +import pl.szczodrzynski.edziennik.api.v2.template.data.api.TemplateApiSample +import pl.szczodrzynski.edziennik.api.v2.template.data.web.TemplateWebSample +import pl.szczodrzynski.edziennik.api.v2.template.data.web.TemplateWebSample2 +import pl.szczodrzynski.edziennik.utils.Utils + +class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "TemplateData" + } + + private var cancelled = false + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + useEndpoint(data.targetEndpointIds.removeAt(0)) { + if (cancelled) { + onSuccess() + return@useEndpoint + } + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId") + when (endpointId) { + ENDPOINT_TEMPLATE_WEB_SAMPLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + TemplateWebSample(data) { onSuccess() } + } + ENDPOINT_TEMPLATE_WEB_SAMPLE_2 -> { + data.startProgress(R.string.edziennik_progress_endpoint_school_info) + TemplateWebSample2(data) { onSuccess() } + } + ENDPOINT_TEMPLATE_API_SAMPLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + TemplateApiSample(data) { onSuccess() } + } + else -> onSuccess() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateWeb.kt new file mode 100644 index 00000000..9cbfe0f5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/TemplateWeb.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.api.v2.ERROR_REQUEST_FAILURE +import pl.szczodrzynski.edziennik.api.v2.ERROR_RESPONSE_EMPTY +import pl.szczodrzynski.edziennik.api.v2.ERROR_TEMPLATE_WEB_OTHER +import pl.szczodrzynski.edziennik.api.v2.GET +import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.currentTimeUnix + +open class TemplateWeb(open val data: DataTemplate) { + companion object { + private const val TAG = "TemplateWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + /** + * This will be used by all TemplateWeb* endpoints. + * + * You can customize this method's parameters to best fit the implemented e-register. + * Just make sure that [tag] and [onSuccess] is present. + */ + fun webGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject?) -> Unit) { + val json = JsonObject() + json.addProperty("foo", "bar") + json.addProperty("sample", "text") + + if (currentTimeUnix() % 4L == 0L) { + // let's set a 20% chance of error, just as a test + data.error(ApiError(tag, ERROR_TEMPLATE_WEB_OTHER) + .withApiResponse("404 Not Found - this is the text returned by the API")) + return + } + + onSuccess(json) + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/api/TemplateApiSample.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/api/TemplateApiSample.kt new file mode 100644 index 00000000..7a5875d2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/api/TemplateApiSample.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data.api + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_API_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.api.v2.template.data.TemplateApi +import pl.szczodrzynski.edziennik.api.v2.template.data.TemplateWeb +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS + +class TemplateApiSample(override val data: DataTemplate, + val onSuccess: () -> Unit) : TemplateApi(data) { + companion object { + private const val TAG = "TemplateApiSample" + } + + init { + apiGet(TAG, "/api/v3/getData.php") { json -> + // here you can access and update any fields of the `data` object + + // ================ + // schedule a sync: + + // not sooner than two days later + data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, 2 * DAY) + // in two days OR on explicit "grades" sync + data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, 2 * DAY, MainActivity.DRAWER_ITEM_GRADES) + // only if sync is executed on Home view + data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, syncIn = null, viewId = MainActivity.DRAWER_ITEM_HOME) + // always, in every sync + data.setSyncNext(ENDPOINT_TEMPLATE_API_SAMPLE, SYNC_ALWAYS) + + onSuccess() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample.kt new file mode 100644 index 00000000..2aacdc7f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data.web + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.api.v2.template.data.TemplateWeb +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS + +class TemplateWebSample(override val data: DataTemplate, + val onSuccess: () -> Unit) : TemplateWeb(data) { + companion object { + private const val TAG = "TemplateWebSample" + } + + init { + webGet(TAG, "/api/v3/getData.php") { json -> + // here you can access and update any fields of the `data` object + + // ================ + // schedule a sync: + + // not sooner than two days later + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, 2 * DAY) + // in two days OR on explicit "grades" sync + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, 2 * DAY, DRAWER_ITEM_GRADES) + // only if sync is executed on Home view + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, syncIn = null, viewId = DRAWER_ITEM_HOME) + // always, in every sync + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE, SYNC_ALWAYS) + + onSuccess() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample2.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample2.kt new file mode 100644 index 00000000..80f5b1c4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/data/web/TemplateWebSample2.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.data.web + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE +import pl.szczodrzynski.edziennik.api.v2.ENDPOINT_TEMPLATE_WEB_SAMPLE_2 +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.api.v2.template.data.TemplateWeb +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS + +class TemplateWebSample2(override val data: DataTemplate, + val onSuccess: () -> Unit) : TemplateWeb(data) { + companion object { + private const val TAG = "TemplateWebSample2" + } + + init { + webGet(TAG, "/api/v3/getData.php") { json -> + // here you can access and update any fields of the `data` object + + // ================ + // schedule a sync: + + // not sooner than two days later + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, 2 * DAY) + // in two days OR on explicit "grades" sync + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, 2 * DAY, MainActivity.DRAWER_ITEM_GRADES) + // only if sync is executed on Home view + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, syncIn = null, viewId = MainActivity.DRAWER_ITEM_HOME) + // always, in every sync + data.setSyncNext(ENDPOINT_TEMPLATE_WEB_SAMPLE_2, SYNC_ALWAYS) + + onSuccess() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLogin.kt new file mode 100644 index 00000000..283ea9b7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLogin.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.api.v2.* +import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi +import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages +import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.utils.Utils + +class TemplateLogin(val data: DataTemplate, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "TemplateLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + if (cancelled) { + onSuccess() + return@useLoginMethod + } + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_TEMPLATE_WEB -> { + data.startProgress(R.string.edziennik_progress_login_template_web) + TemplateLoginWeb(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_TEMPLATE_API -> { + data.startProgress(R.string.edziennik_progress_login_template_api) + TemplateLoginApi(data) { onSuccess(loginMethodId) } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginApi.kt new file mode 100644 index 00000000..777c0157 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginApi.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.login + +import okhttp3.Cookie +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.HOUR +import pl.szczodrzynski.edziennik.api.v2.ERROR_LOGIN_DATA_MISSING +import pl.szczodrzynski.edziennik.api.v2.ERROR_PROFILE_MISSING +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.currentTimeUnix + +class TemplateLoginApi(val data: DataTemplate, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "TemplateLoginApi" + } + + init { run { + if (data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return@run + } + + if (data.isApiLoginValid()) { + onSuccess() + } + else { + if (/*data.webLogin != null && data.webPassword != null && */true) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + fun loginWithCredentials() { + // succeed immediately + + data.apiToken = "ThisIsAVeryLongToken" + data.apiExpiryTime = currentTimeUnix() + 24 * HOUR + onSuccess() + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginWeb.kt new file mode 100644 index 00000000..14837bbc --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/template/login/TemplateLoginWeb.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.api.v2.template.login + +import okhttp3.Cookie +import pl.szczodrzynski.edziennik.api.v2.ERROR_LOGIN_DATA_MISSING +import pl.szczodrzynski.edziennik.api.v2.ERROR_PROFILE_MISSING +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_API +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.api.v2.template.data.DataTemplate +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.getUnixDate + +class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "TemplateLoginWeb" + } + + init { run { + if (data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return@run + } + + if (data.isWebLoginValid()) { + data.app.cookieJar.saveFromResponse(null, listOf( + Cookie.Builder() + .name("AuthCookie") + .value(data.webCookie!!) + .domain("eregister.example.com") + .secure().httpOnly().build() + )) + onSuccess() + } + else { + data.app.cookieJar.clearForDomain("eregister.example.com") + if (/*data.webLogin != null && data.webPassword != null && */true) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + fun loginWithCredentials() { + // succeed immediately + + data.webCookie = "ThisIsACookie" + data.webExpiryTime = currentTimeUnix() + 45 * 60 /* 45min */ + onSuccess() + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcdd253f..0b9ced5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -926,4 +926,6 @@ Pokazuj nieobecności nauczycieli w Terminarzu Pobieram informacje o szkole... Pobieranie ocen ucznia... + Logowanie do Template WEB... + Logowanie do Template API...