[Vulcan/Hebe] Add getting timetable and lesson changes.

This commit is contained in:
Kuba Szczodrzyński 2021-02-21 16:18:33 +01:00
parent d6a67a0da6
commit 54e49af943
8 changed files with 285 additions and 19 deletions

View File

@ -127,6 +127,8 @@ const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get"
const val VULCAN_HEBE_ENDPOINT_REGISTER_NEW = "api/mobile/register/new"
const val VULCAN_HEBE_ENDPOINT_MAIN = "api/mobile/register/hebe"
const val VULCAN_HEBE_ENDPOINT_TIMETABLE = "api/mobile/schedule"
const val VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES = "api/mobile/schedule/changes"
const val VULCAN_HEBE_ENDPOINT_EXAMS = "api/mobile/exam"
const val VULCAN_HEBE_ENDPOINT_GRADES = "api/mobile/grade"
const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework"

View File

@ -21,6 +21,7 @@ const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090
const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100
const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
const val ENDPOINT_VULCAN_HEBE_MAIN = 3000
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060
@ -30,6 +31,9 @@ val VulcanFeatures = listOf(
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
ENDPOINT_VULCAN_API_TIMETABLE to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
ENDPOINT_VULCAN_HEBE_TIMETABLE to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// agenda
Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf(
ENDPOINT_VULCAN_API_EVENTS to LOGIN_METHOD_VULCAN_API

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeExams
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeGrades
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeHomework
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeTimetable
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber
import pl.szczodrzynski.edziennik.utils.Utils
@ -94,6 +95,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
VulcanWebLuckyNumber(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
VulcanHebeTimetable(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_EXAMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
VulcanHebeExams(data, lastSync, onSuccess)

View File

@ -15,11 +15,13 @@ import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.HebeFilterType
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.LessonRange
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.net.HttpURLConnection
import java.net.URLEncoder
import java.time.Instant
@ -50,9 +52,9 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return date.getString("Date")?.let { Date.fromY_m_d(it) }
}
fun getTeacherId(json: JsonObject?, key: String): Long {
fun getTeacherId(json: JsonObject?, key: String): Long? {
val teacher = json.getJsonObject(key)
val teacherId = teacher.getLong("Id") ?: return -1
val teacherId = teacher.getLong("Id") ?: return null
if (data.teacherList[teacherId] == null) {
data.teacherList[teacherId] = Teacher(
data.profileId,
@ -64,9 +66,9 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return teacherId
}
fun getSubjectId(json: JsonObject?, key: String): Long {
fun getSubjectId(json: JsonObject?, key: String): Long? {
val subject = json.getJsonObject(key)
val subjectId = subject.getLong("Id") ?: return -1
val subjectId = subject.getLong("Id") ?: return null
if (data.subjectList[subjectId] == null) {
data.subjectList[subjectId] = Subject(
data.profileId,
@ -117,6 +119,21 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return teamId
}
fun getLessonRange(json: JsonObject?, key: String): LessonRange? {
val timeslot = json.getJsonObject(key)
val position = timeslot.getInt("Position") ?: return null
val start = timeslot.getString("Start") ?: return null
val end = timeslot.getString("End") ?: return null
val lessonRange = LessonRange(
data.profileId,
position,
Time.fromH_m(start),
Time.fromH_m(end)
)
data.lessonRanges[position] = lessonRange
return lessonRange
}
fun getSemester(json: JsonObject?): Int {
val periodId = json.getInt("PeriodId") ?: return 1
return if (periodId == data.semester1Id)

View File

@ -5,7 +5,6 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -29,8 +28,8 @@ class VulcanHebeExams(
list.forEach { exam ->
val id = exam.getLong("Id") ?: return@forEach
val eventDate = getDate(exam, "Deadline") ?: return@forEach
val subjectId = getSubjectId(exam, "Subject")
val teacherId = getTeacherId(exam, "Creator")
val subjectId = getSubjectId(exam, "Subject") ?: -1
val teacherId = getTeacherId(exam, "Creator") ?: -1
val teamId = getTeamId(exam, "Distribution")
?: getClassId(exam, "Class")
?: data.teamClass?.id
@ -72,7 +71,6 @@ class VulcanHebeExams(
)
}
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_HEBE_EXAMS, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_HEBE_EXAMS)
}

View File

@ -5,7 +5,6 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -35,8 +34,8 @@ class VulcanHebeGrades(
val category = column.getJsonObject("Category")
val categoryText = category.getString("Name")
val teacherId = getTeacherId(grade, "Creator")
val subjectId = getSubjectId(column, "Subject")
val teacherId = getTeacherId(grade, "Creator") ?: -1
val subjectId = getSubjectId(column, "Subject") ?: -1
val description = column.getString("Name")
val comment = grade.getString("Comment")
@ -115,10 +114,6 @@ class VulcanHebeGrades(
)
}
data.toRemove.add(
DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber,
Grade.TYPE_NORMAL
))
data.setSyncNext(ENDPOINT_VULCAN_HEBE_GRADES, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_HEBE_GRADES)
}

View File

@ -4,7 +4,6 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -30,8 +29,8 @@ class VulcanHebeHomework(
list.forEach { exam ->
val id = exam.getLong("IdHomework") ?: return@forEach
val eventDate = getDate(exam, "Deadline") ?: return@forEach
val subjectId = getSubjectId(exam, "Subject")
val teacherId = getTeacherId(exam, "Creator")
val subjectId = getSubjectId(exam, "Subject") ?: -1
val teacherId = getTeacherId(exam, "Creator") ?: -1
val teamId = data.teamClass?.id ?: -1
val topic = exam.getString("Content")?.trim() ?: ""
@ -63,7 +62,6 @@ class VulcanHebeHomework(
)
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_HEBE_HOMEWORK, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_HEBE_HOMEWORK)
}

View File

@ -0,0 +1,247 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_SOURCE
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_SHIFTED_TARGET
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class VulcanHebeTimetable(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeTimetable"
}
private val lessonList = mutableListOf<Lesson>()
private val lessonDates = mutableSetOf<Int>()
init {
val previousWeekStart = Week.getWeekStart().stepForward(0, 0, -7)
if (Date.getToday().weekDay > 4) {
previousWeekStart.stepForward(0, 0, 7)
}
val dateFrom = data.arguments
?.getString("weekStart")
?.let { Date.fromY_m_d(it) }
?: previousWeekStart
val dateTo = dateFrom.clone().stepForward(0, 0, 13)
val lastSync = null
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_TIMETABLE,
HebeFilterType.BY_PUPIL,
dateFrom = dateFrom,
dateTo = dateTo,
lastSync = lastSync
) { lessons, _ ->
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES,
HebeFilterType.BY_PUPIL,
dateFrom = dateFrom,
dateTo = dateTo,
lastSync = lastSync
) { changes, _ ->
processData(lessons, changes)
// cancel lesson changes when caused by a shift
for (lesson in lessonList) {
if (lesson.type != TYPE_SHIFTED_TARGET)
continue
lessonList.firstOrNull {
it.oldDate == lesson.date
&& it.oldLessonNumber == lesson.lessonNumber
&& it.type == TYPE_CHANGE
}?.let {
it.type = TYPE_CANCELLED
it.date = null
it.lessonNumber = null
it.startTime = null
it.endTime = null
it.subjectId = null
it.teacherId = null
it.teamId = null
it.classroom = null
}
}
// add TYPE_NO_LESSONS to empty dates
val date: Date = dateFrom.clone()
while (date <= dateTo) {
if (!lessonDates.contains(date.value)) {
lessonList.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(
TAG,
"Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}"
)
data.lessonList.addAll(lessonList)
data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_HEBE_TIMETABLE)
}
}
}
private fun buildLesson(changes: List<JsonObject>, json: JsonObject): Pair<Lesson, Lesson?>? {
val lesson = Lesson(profileId, -1)
var lessonShift: Lesson? = null
val lessonDate = getDate(json, "Date") ?: return null
val lessonRange = getLessonRange(json, "TimeSlot")
val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = getTeacherId(json, "TeacherPrimary")
val classroom = json.getString("Room")
val subjectId = getSubjectId(json, "Subject")
val teamId = getTeamId(json, "Distribution")
?: getClassId(json, "Clazz")
?: data.teamClass?.id
?: -1
val change = json.getJsonObject("Change")
val changeId = change.getInt("Id")
val type = when (change.getInt("Type")) {
1 -> TYPE_CANCELLED
2 -> TYPE_CHANGE
3 -> TYPE_SHIFTED_SOURCE
4 -> TYPE_CANCELLED // TODO: 2021-02-21 add showing cancellation reason
else -> TYPE_NORMAL
}
lesson.type = type
if (type == TYPE_NORMAL) {
lesson.date = lessonDate
lesson.lessonNumber = lessonRange?.lessonNumber
lesson.startTime = startTime
lesson.endTime = endTime
lesson.subjectId = subjectId
lesson.teacherId = teacherId
lesson.teamId = teamId
lesson.classroom = classroom
} else {
lesson.oldDate = lessonDate
lesson.oldLessonNumber = lessonRange?.lessonNumber
lesson.oldStartTime = startTime
lesson.oldEndTime = endTime
lesson.oldSubjectId = subjectId
lesson.oldTeacherId = teacherId
lesson.oldTeamId = teamId
lesson.oldClassroom = classroom
}
if (type == TYPE_CHANGE || type == TYPE_SHIFTED_SOURCE) {
val changeJson = changes.firstOrNull {
it.getInt("Id") == changeId
} ?: return lesson to null
val changeLessonDate = getDate(changeJson, "LessonDate") ?: return lesson to null
val changeLessonRange = getLessonRange(changeJson, "TimeSlot") ?: lessonRange
val changeStartTime = changeLessonRange?.startTime
val changeEndTime = changeLessonRange?.endTime
val changeTeacherId = getTeacherId(changeJson, "TeacherPrimary") ?: teacherId
val changeClassroom = changeJson.getString("Room") ?: classroom
val changeSubjectId = getSubjectId(changeJson, "Subject") ?: subjectId
val changeTeamId = getTeamId(json, "Distribution")
?: getClassId(json, "Clazz")
?: teamId
if (type != TYPE_CHANGE) {
/* lesson shifted */
lessonShift = Lesson(profileId, -1)
lessonShift.type = TYPE_SHIFTED_TARGET
// update source lesson with the target lesson date
lesson.date = changeLessonDate
lesson.lessonNumber = changeLessonRange?.lessonNumber
lesson.startTime = changeStartTime
lesson.endTime = changeEndTime
// update target lesson with the source lesson date
lessonShift.oldDate = lessonDate
lessonShift.oldLessonNumber = lessonRange?.lessonNumber
lessonShift.oldStartTime = startTime
lessonShift.oldEndTime = endTime
}
(if (type == TYPE_CHANGE) lesson else lessonShift)
?.apply {
this.date = changeLessonDate
this.lessonNumber = changeLessonRange?.lessonNumber
this.startTime = changeStartTime
this.endTime = changeEndTime
this.subjectId = changeSubjectId
this.teacherId = changeTeacherId
this.teamId = changeTeamId
this.classroom = changeClassroom
}
}
return lesson to lessonShift
}
private fun processData(lessons: List<JsonObject>, changes: List<JsonObject>) {
lessons.forEach { lessonJson ->
if (lessonJson.getBoolean("Visible") != true)
return@forEach
val lessonPair = buildLesson(changes, lessonJson) ?: return@forEach
val (lessonObject, lessonShift) = lessonPair
when {
lessonShift != null -> lessonShift
lessonObject.type != TYPE_NORMAL -> lessonObject
else -> null
}?.let { lesson ->
val lessonDate = lesson.displayDate ?: return@let
val seen = profile?.empty ?: true || lessonDate < Date.getToday()
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lesson.id,
seen,
seen
)
)
}
lessonObject.id = lessonObject.buildId()
lessonShift?.id = lessonShift?.buildId() ?: -1
lessonList.add(lessonObject)
lessonShift?.let { lessonList.add(it) }
lessonObject.displayDate?.let { lessonDates.add(it.value) }
lessonShift?.displayDate?.let { lessonDates.add(it.value) }
}
}
}