Timetable fixes

- Only one request is made to get the timetable HTML document for parsing the timetable and the weekNumberYear
- On timeouts or other errors, the cached data won't be overwritten with emptiness anymore
- The scheduled updates will now also update the weekNumberYear
This commit is contained in:
Hannes Braun 2021-10-13 15:57:26 +02:00
parent ae9bf2a562
commit 22f17d10e0
Signed by: hannesbraun
GPG Key ID: 7B6557E1DFD685BE
4 changed files with 66 additions and 42 deletions

View File

@ -58,7 +58,7 @@ data class TimetableDay(val timeslots: Array<ArrayList<Lesson>> = Array(6) { Arr
data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() }) data class TimetableWeek(val days: Array<TimetableDay> = Array(6) { TimetableDay() })
data class TimetableCourseMeta(var updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, val weekNumberYear: Int = 0, val link: String = "") data class TimetableCourseMeta(var updateTime: Long = 0, val courseName: String = "", val weekIndex: Int = 0, var weekNumberYear: Int = 0, val link: String = "")
data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek()) data class TimetableCourseWeek(val meta: TimetableCourseMeta = TimetableCourseMeta(), var timetable: TimetableWeek = TimetableWeek())

View File

@ -59,7 +59,7 @@ class CacheController {
* @param weekIndex request week number (current week = 0) * @param weekIndex request week number (current week = 0)
* @return timetable of the course (Type: [TimetableCourseWeek]) * @return timetable of the course (Type: [TimetableCourseWeek])
*/ */
fun getTimetable(courseName: String, weekIndex: Int): TimetableCourseWeek = runBlocking { fun getTimetable(courseName: String, weekIndex: Int): TimetableCourseWeek {
// TODO just for testing // TODO just for testing
if (courseName == "TEST_A" || courseName == "TEST_B") { if (courseName == "TEST_A" || courseName == "TEST_B") {
@ -68,27 +68,43 @@ class CacheController {
val weekNumberYear = 0 val weekNumberYear = 0
val instr = javaClass.getResourceAsStream("/html/Timetable_normal-week.html") val instr = javaClass.getResourceAsStream("/html/Timetable_normal-week.html")
val htmlDoc = Jsoup.parse(instr,"UTF-8", "https://www.hs-offenburg.de/") val timetableParser =
val timetableTest = TimetableParser().parseTimeTable(htmlDoc) TimetableParser(htmlDoc = Jsoup.parse(instr, "UTF-8", "https://www.hs-offenburg.de/"))
val timetableTest = timetableParser.parseTimeTable()
return@runBlocking TimetableCourseWeek(TimetableCourseMeta(currentTime, courseName, weekIndex, weekNumberYear, timetableLink), timetableTest) return TimetableCourseWeek(
TimetableCourseMeta(
currentTime,
courseName,
weekIndex,
weekNumberYear,
timetableLink
), timetableTest ?: TimetableWeek()
)
} }
return@runBlocking timetableList.getOrPut("$courseName-$weekIndex") { val key = "$courseName-$weekIndex"
return if (timetableList.contains(key)) {
timetableList[key]!!
} else {
val timetableLink = courseList.courses[courseName] val timetableLink = courseList.courses[courseName]
?.courseLink ?.courseLink
?.replace("week=0", "week=$weekIndex") ?: "" ?.replace("week=0", "week=$weekIndex") ?: ""
val currentTime = System.currentTimeMillis() / 1000 val currentTime = System.currentTimeMillis() / 1000
var timetable = TimetableWeek()
var weekNumberYear = 0
val timetableJobs = listOf( val timetableParser = TimetableParser(timetableLink)
async { timetable = TimetableParser().getTimeTable(timetableLink) }, val weekNumberYear = timetableParser.parseWeekNumberYear()
async { weekNumberYear = TimetableParser().getWeekNumberYear(timetableLink) } val timetable = timetableParser.parseTimeTable()
)
timetableJobs.awaitAll() TimetableCourseWeek(
TimetableCourseWeek(TimetableCourseMeta(currentTime, courseName, weekIndex, weekNumberYear, timetableLink), timetable) TimetableCourseMeta(
currentTime,
courseName,
weekIndex,
weekNumberYear ?: 0,
timetableLink
), timetable ?: TimetableWeek()
).also { timetableList[key] = it }
} }
} }
@ -177,7 +193,10 @@ class CacheController {
try { try {
timetableList.forEach { timetableCourse -> timetableList.forEach { timetableCourse ->
executor.execute { executor.execute {
timetableCourse.value.timetable = TimetableParser().getTimeTable(timetableCourse.value.meta.link) val timetableParser = TimetableParser(timetableCourse.value.meta.link)
timetableCourse.value.timetable = timetableParser.parseTimeTable() ?: return@execute
timetableCourse.value.meta.weekNumberYear =
timetableParser.parseWeekNumberYear() ?: return@execute
timetableCourse.value.meta.updateTime = System.currentTimeMillis() / 1000 timetableCourse.value.meta.updateTime = System.currentTimeMillis() / 1000
saveTimetableToCache(timetableCourse.value) // save the updated timetable to the cache directory saveTimetableToCache(timetableCourse.value) // save the updated timetable to the cache directory

View File

@ -28,26 +28,37 @@ import org.mosad.thecitadelofricks.Lesson
import org.mosad.thecitadelofricks.TimetableWeek import org.mosad.thecitadelofricks.TimetableWeek
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class TimetableParser { /**
* @param timetableURL the URL of the timetable you want to get
* @param htmlDoc the html document to use (the timetableURL will be ignored if this value is present)
*/
class TimetableParser(timetableURL: String? = null, htmlDoc: Document? = null) {
private var logger: org.slf4j.Logger = LoggerFactory.getLogger(TimetableParser::class.java) private var logger: org.slf4j.Logger = LoggerFactory.getLogger(TimetableParser::class.java)
private val days = arrayOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") private val days = arrayOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
private val htmlDoc: Document? =
htmlDoc
?: if (timetableURL == null) {
null
} else {
try {
Jsoup.connect(timetableURL).get()
} catch (gex: Exception) {
logger.error("general TimetableParser error", gex)
null
}
}
/** /**
* get the timetable from the given url * parse the timetable from the previously given url
* the timetable is organised per row not per column; * the timetable is organised per row not per column;
* Mon 1, Tue 1, Wed 1, Thur 1, Fri 1, Sat 1, Mon 2 and so on * Mon 1, Tue 1, Wed 1, Thur 1, Fri 1, Sat 1, Mon 2 and so on
* @param timetableURL the URL of the timetable you want to get
*/ */
fun getTimeTable(timetableURL: String): TimetableWeek { fun parseTimeTable(): TimetableWeek? {
return try { if (htmlDoc == null) {
parseTimeTable(Jsoup.connect(timetableURL).get()) return null
} catch (gex: Exception) {
logger.error("general TimetableParser error", gex)
TimetableWeek()
} }
}
fun parseTimeTable(htmlDoc: Document): TimetableWeek {
val timetableWeek = TimetableWeek() val timetableWeek = TimetableWeek()
val rows = htmlDoc.select("table.timetable").select("tr[scope=\"row\"]") val rows = htmlDoc.select("table.timetable").select("tr[scope=\"row\"]")
@ -117,19 +128,13 @@ class TimetableParser {
} }
/** /**
* get the week number of the year for the timetable * parse the week number of the year for the timetable
* @param timetableURL the URL of the timetable you want to get
*/ */
fun getWeekNumberYear(timetableURL: String): Int { fun parseWeekNumberYear(): Int? {
return try { if (htmlDoc == null) {
parseWeekNumberYear(Jsoup.connect(timetableURL).get()) return null
} catch (gex: Exception) {
logger.error("general TimetableParser error", gex)
0
} }
}
fun parseWeekNumberYear(htmlDoc: Document): Int {
return htmlDoc.select("h1.timetable-caption").text().substringAfter("- ") return htmlDoc.select("h1.timetable-caption").text().substringAfter("- ")
.substringBefore(".").replace(" ", "").toInt() .substringBefore(".").replace(" ", "").toInt()
} }

View File

@ -32,8 +32,8 @@ class TimetableParserTest {
@Test @Test
fun parseTimetableNormalWeek() { fun parseTimetableNormalWeek() {
val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_normal-week.html").path) val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_normal-week.html").path)
val htmlDoc = Jsoup.parse(htmlFile,"UTF-8", "https://www.hs-offenburg.de/") val htmlDoc = Jsoup.parse(htmlFile, "UTF-8", "https://www.hs-offenburg.de/")
val actualTimetable = TimetableParser().parseTimeTable(htmlDoc).toString().trim() val actualTimetable = TimetableParser(htmlDoc = htmlDoc).parseTimeTable().toString().trim()
val expectedTimetable = TimetableParserTest::class.java.getResource("/expected/Timetable_normal-week.txt").readText().trim() val expectedTimetable = TimetableParserTest::class.java.getResource("/expected/Timetable_normal-week.txt").readText().trim()
Assertions.assertEquals(expectedTimetable, actualTimetable) Assertions.assertEquals(expectedTimetable, actualTimetable)
@ -42,8 +42,8 @@ class TimetableParserTest {
@Test @Test
fun parseTimetableEmptyWeek() { fun parseTimetableEmptyWeek() {
val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_empty-week.html").path) val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_empty-week.html").path)
val htmlDoc = Jsoup.parse(htmlFile,"UTF-8", "https://www.hs-offenburg.de/") val htmlDoc = Jsoup.parse(htmlFile, "UTF-8", "https://www.hs-offenburg.de/")
val actualTimetable = TimetableParser().parseTimeTable(htmlDoc).toString().trim() val actualTimetable = TimetableParser(htmlDoc = htmlDoc).parseTimeTable().toString().trim()
val expectedTimetable = TimetableParserTest::class.java.getResource("/expected/Timetable_empty-week.txt").readText().trim() val expectedTimetable = TimetableParserTest::class.java.getResource("/expected/Timetable_empty-week.txt").readText().trim()
Assertions.assertEquals(expectedTimetable, actualTimetable) Assertions.assertEquals(expectedTimetable, actualTimetable)
@ -52,8 +52,8 @@ class TimetableParserTest {
@Test @Test
fun parseWeekNumberYear() { fun parseWeekNumberYear() {
val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_normal-week.html").path) val htmlFile = File(TimetableParserTest::class.java.getResource("/html/Timetable_normal-week.html").path)
val htmlDoc = Jsoup.parse(htmlFile,"UTF-8", "https://www.hs-offenburg.de/") val htmlDoc = Jsoup.parse(htmlFile, "UTF-8", "https://www.hs-offenburg.de/")
val actualWeekNumberYear = TimetableParser().parseWeekNumberYear(htmlDoc) val actualWeekNumberYear = TimetableParser(htmlDoc = htmlDoc).parseWeekNumberYear()
Assertions.assertEquals(42, actualWeekNumberYear) Assertions.assertEquals(42, actualWeekNumberYear)
} }