19 Commits

Author SHA1 Message Date
f49e600613 version 1.3.0
All checks were successful
continuous-integration/woodpecker the build was successful
2021-10-31 22:09:12 +01:00
940a30e9aa Merge pull request 'Parse the year of a timetable' (#21) from hannesbraun/TheCitadelofRicks:feature/parse-year into master
All checks were successful
continuous-integration/woodpecker the build was successful
Reviewed-on: #21
2021-10-24 15:00:13 +02:00
d863f4fb1e TimetableParser: parse year
All checks were successful
continuous-integration/woodpecker the build was successful
This also adds the parsed year to the meta object included in the timetable response
2021-10-24 14:39:22 +02:00
dc57a0d0c1 Merge pull request 'Various improvements' (#17) from hannesbraun/TheCitadelofRicks:fix/timeout-resilience into master
All checks were successful
continuous-integration/woodpecker the build was successful
Reviewed-on: #17
2021-10-24 14:38:12 +02:00
5ba9dfc263 Better null checks 2021-10-24 14:34:00 +02:00
ca8efdaa85 Only add timetable to cache on success 2021-10-24 14:34:00 +02:00
8e3af696e0 Limit sending timetable requests in parallel to 3
Otherwise, the requests may fail (from my experience). Also we don't want to look suspicious (flooding their server with tons of requests at the same time).
2021-10-24 14:34:00 +02:00
fb6291792d Use ConcurrentHashMap for timetableList
Multiple requests may be processed at the same time and could otherwise cause problems (concurrent write operations)
2021-10-24 14:34:00 +02:00
993b8f6a71 Small improvements
- Improve formatting
- Fix some typos
- Mini code improvements
2021-10-24 14:34:00 +02:00
f9cc9b5e14 Make the update scheduling more readable (hopefully) 2021-10-24 14:34:00 +02:00
460d1ee131 StatusController: use properties instead of getters 2021-10-24 14:33:59 +02:00
90847a2730 Also set JVM target to 11 for Java 2021-10-24 14:33:59 +02:00
a292b45fcb Dependency updates
This also replaces JCenter with Maven Central since JCenter is now read-only
2021-10-24 14:33:59 +02:00
22f17d10e0 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
2021-10-24 14:33:59 +02:00
ae9bf2a562 Update Kotlin to 1.5.31 2021-10-24 14:33:58 +02:00
6394a7c880 fix secrets (1st try)
All checks were successful
continuous-integration/drone the build was successful
2021-10-16 15:48:34 +02:00
5ab5e850bd execute the docker build/deploy image with privileged: true
Some checks failed
continuous-integration/drone the build failed
2021-10-16 15:21:01 +02:00
a97a464a83 Merge pull request 'use techknowlogick's drone-docker image' (#20) from fix/docker-build into master
Some checks failed
continuous-integration/drone the build failed
Reviewed-on: #20
2021-10-16 14:39:38 +02:00
2b06efeece use techknowlogick's drone-docker image
All checks were successful
continuous-integration/drone the build was successful
this should fix the docker build issues
2021-10-16 14:35:37 +02:00
7 changed files with 50 additions and 41 deletions

View File

@ -11,12 +11,11 @@ pipeline:
event: event:
- tag - tag
docker: docker:
image: plugins/docker image: techknowlogick/drone-docker
privileged: true
repo: mosadxyz/tcor repo: mosadxyz/tcor
secrets: [docker_username, docker_password]
tags: latest tags: latest
secrets:
- username: docker_username
password: docker_password
when: when:
event: event:
- tag - tag

View File

@ -6,7 +6,7 @@ plugins {
} }
group 'org.mosad' group 'org.mosad'
version '1.2.7' version '1.3.0'
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -48,8 +48,8 @@ class APIController {
private val logger: Logger = LoggerFactory.getLogger(APIController::class.java) private val logger: Logger = LoggerFactory.getLogger(APIController::class.java)
companion object { companion object {
const val apiVersion = "1.2.0" const val apiVersion = "1.3.0"
const val softwareVersion = "1.2.8" const val softwareVersion = "1.3.0"
val startTime = System.currentTimeMillis() / 1000 val startTime = System.currentTimeMillis() / 1000
} }

View File

@ -46,6 +46,9 @@ data class MensaMeta(val updateTime: Long, val mensaName: String)
data class MensaMenu(val meta: MensaMeta, val currentWeek: MensaWeek, val nextWeek: MensaWeek) data class MensaMenu(val meta: MensaMeta, val currentWeek: MensaWeek, val nextWeek: MensaWeek)
// data classes for the timetable part // data classes for the timetable part
data class CalendarWeek(val week: Int, val year: Int)
data class Lesson( data class Lesson(
val lessonID: String, val lessonID: String,
val lessonSubject: String, val lessonSubject: String,
@ -58,7 +61,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, var 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 year: 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

@ -33,9 +33,9 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.* import java.io.*
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.concurrent.scheduleAtFixedRate import kotlin.concurrent.scheduleAtFixedRate
import kotlin.time.Duration import kotlin.time.Duration
@ -53,7 +53,7 @@ class CacheController {
var courseList = CoursesList(CoursesMeta(), sortedMapOf()) var courseList = CoursesList(CoursesMeta(), sortedMapOf())
var mensaMenu = MensaMenu(MensaMeta(0, ""), MensaWeek(), MensaWeek()) var mensaMenu = MensaMenu(MensaMeta(0, ""), MensaWeek(), MensaWeek())
var timetableList = HashMap<String, TimetableCourseWeek>() // this list contains all timetables var timetableList = ConcurrentHashMap<String, TimetableCourseWeek>() // this list contains all timetables
/** /**
* get a timetable, since they may not be cached, we need to make sure it's cached, otherwise download * get a timetable, since they may not be cached, we need to make sure it's cached, otherwise download
@ -68,6 +68,7 @@ class CacheController {
val currentTime = System.currentTimeMillis() / 1000 val currentTime = System.currentTimeMillis() / 1000
val timetableLink = "https://mosad.xyz" val timetableLink = "https://mosad.xyz"
val weekNumberYear = 0 val weekNumberYear = 0
val year = 0
val instr = CacheController::class.java.getResourceAsStream("/html/Timetable_normal-week.html") val instr = CacheController::class.java.getResourceAsStream("/html/Timetable_normal-week.html")
val timetableParser = val timetableParser =
@ -80,6 +81,7 @@ class CacheController {
courseName, courseName,
weekIndex, weekIndex,
weekNumberYear, weekNumberYear,
year,
timetableLink timetableLink
), timetableTest ?: TimetableWeek() ), timetableTest ?: TimetableWeek()
) )
@ -95,7 +97,7 @@ class CacheController {
val currentTime = System.currentTimeMillis() / 1000 val currentTime = System.currentTimeMillis() / 1000
val timetableParser = TimetableParser(timetableLink) val timetableParser = TimetableParser(timetableLink)
val weekNumberYear = timetableParser.parseWeekNumberYear() val calendarWeek = timetableParser.parseCalendarWeek()
val timetable = timetableParser.parseTimeTable() val timetable = timetableParser.parseTimeTable()
TimetableCourseWeek( TimetableCourseWeek(
@ -103,10 +105,11 @@ class CacheController {
currentTime, currentTime,
courseName, courseName,
weekIndex, weekIndex,
weekNumberYear ?: 0, calendarWeek?.week ?: 0,
calendarWeek?.year ?: 0,
timetableLink timetableLink
), timetable ?: TimetableWeek() ), timetable ?: TimetableWeek()
).also { timetableList[key] = it } ).also { if (timetable != null) timetableList[key] = it }
} }
} }
@ -198,7 +201,7 @@ class CacheController {
val timetableParser = TimetableParser(timetableCourse.value.meta.link) val timetableParser = TimetableParser(timetableCourse.value.meta.link)
timetableCourse.value.timetable = timetableParser.parseTimeTable() ?: return@execute timetableCourse.value.timetable = timetableParser.parseTimeTable() ?: return@execute
timetableCourse.value.meta.weekNumberYear = timetableCourse.value.meta.weekNumberYear =
timetableParser.parseWeekNumberYear() ?: return@execute timetableParser.parseCalendarWeek()?.week ?: 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

@ -22,8 +22,11 @@
package org.mosad.thecitadelofricks.hsoparser package org.mosad.thecitadelofricks.hsoparser
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.mosad.thecitadelofricks.CalendarWeek
import org.mosad.thecitadelofricks.Lesson import org.mosad.thecitadelofricks.Lesson
import org.mosad.thecitadelofricks.TimetableWeek import org.mosad.thecitadelofricks.TimetableWeek
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -36,31 +39,33 @@ 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? = companion object {
htmlDoc val semaphore = Semaphore(3, 0)
?: if (timetableURL == null) { }
private val htmlDoc: Document? = htmlDoc ?: timetableURL?.let {
runBlocking {
try {
// Only allow sending a limited amount of requests at the same time
semaphore.acquire()
Jsoup.connect(timetableURL).get()
} catch (gex: Exception) {
logger.error("general TimetableParser error", gex)
null null
} else { } finally {
try { semaphore.release()
Jsoup.connect(timetableURL).get()
} catch (gex: Exception) {
logger.error("general TimetableParser error", gex)
null
}
} }
}
}
/** /**
* parse the timetable from the previously 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
*/ */
fun parseTimeTable(): TimetableWeek? { fun parseTimeTable(): TimetableWeek? = htmlDoc?.let {
if (htmlDoc == null) {
return null
}
val timetableWeek = TimetableWeek() val timetableWeek = TimetableWeek()
val rows = htmlDoc.select("table.timetable").select("tr[scope=\"row\"]") val rows = it.select("table.timetable").select("tr[scope=\"row\"]")
var sDay = -1 var sDay = -1
var sRow = -1 var sRow = -1
@ -127,15 +132,13 @@ class TimetableParser(timetableURL: String? = null, htmlDoc: Document? = null) {
} }
/** /**
* parse the week number of the year for the timetable * parse the calendar week and the associated year for the timetable
*/ */
fun parseWeekNumberYear(): Int? { fun parseCalendarWeek(): CalendarWeek? = htmlDoc?.let {
if (htmlDoc == null) { val dateStr = it.select("h1.timetable-caption").text().substringAfter("- ")
return null val week = dateStr.substringBefore(".").replace(" ", "").toInt()
} val year = dateStr.substringAfter("Woche ").replace(" ", "").toInt()
CalendarWeek(week, year)
return htmlDoc.select("h1.timetable-caption").text().substringAfter("- ")
.substringBefore(".").replace(" ", "").toInt()
} }
@Suppress("unused") @Suppress("unused")

View File

@ -25,6 +25,7 @@ package org.mosad.thecitadelofricks.hsoparser
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mosad.thecitadelofricks.CalendarWeek
import java.io.File import java.io.File
class TimetableParserTest { class TimetableParserTest {
@ -50,11 +51,11 @@ class TimetableParserTest {
} }
@Test @Test
fun parseWeekNumberYear() { fun parseCalendarWeek() {
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(htmlDoc = htmlDoc).parseWeekNumberYear() val actualCalendarWeek = TimetableParser(htmlDoc = htmlDoc).parseCalendarWeek()
Assertions.assertEquals(42, actualWeekNumberYear) Assertions.assertEquals(CalendarWeek(42, 2019), actualCalendarWeek)
} }
} }