/** * TheCitadelofRicks * * Copyright 2019 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ package org.mosad.thecitadelofricks import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.mosad.thecitadelofricks.hsoparser.CourseListParser import org.mosad.thecitadelofricks.hsoparser.MensaParser import org.mosad.thecitadelofricks.hsoparser.TimetableParser import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.* import kotlin.collections.ArrayList import kotlin.concurrent.scheduleAtFixedRate class CacheController { private val logger: Logger = LoggerFactory.getLogger(CacheController::class.java) // hso parser links (hardcoded) private val courseListURL = "https://www.hs-offenburg.de/studium/vorlesungsplaene/" private val mensaMenuURL = "https://www.swfr.de/de/essen-trinken/speiseplaene/mensa-offenburg/" private val mensaName = "Offenburg" // cache objects companion object{ lateinit var courseList: CourseList lateinit var mensaMenu: MensaMenu var timetableList = ArrayList() // this list contains all timetables } init { initUpdates() scheduledUpdates() } fun getTimetable(courseName: String, weekIndex: Int): TimetableCourseWeek = runBlocking { val currentTime = System.currentTimeMillis() / 1000 var timetable = TimetableWeek() var weekNumberYear = 0 // check if the timetable already exists and is up to date when (timetableList.stream().filter { x -> x.meta.courseName == courseName && x.meta.weekIndex == weekIndex }.findAny().orElse(null)) { // there is no such course yet, create one null -> { val courseLink = courseList.courses.stream().filter { x -> x.courseName == courseName }.findFirst().orElse(null).courseLink val timetableLink = courseLink.replace("week=0","week=$weekIndex") val jobTimetable = GlobalScope.async { timetable = TimetableParser().getTimeTable(timetableLink) weekNumberYear = TimetableParser().getWeekNumberYear(timetableLink) } jobTimetable.await() timetableList.add(TimetableCourseWeek(TimetableCourseMeta(currentTime, courseName, weekIndex, weekNumberYear, timetableLink), timetable)) logger.info("added new timetable for $courseName, week $weekIndex") } } return@runBlocking timetableList.stream().filter { x -> x.meta.courseName == courseName && x.meta.weekIndex == weekIndex }.findAny().orElse(null) } /** * this function updates the courseList * during the update process the old data will be returned for a API request */ private fun asyncUpdateCourseList() = GlobalScope.launch { val result = CourseListParser().getCourseLinks(courseListURL) if (result != null) { courseList = CourseList(CourseMeta(System.currentTimeMillis() / 1000, result.size), result) } logger.info("updated courses successful at ${Date(courseList.meta.updateTime * 1000)}") } /** * this function updates the mensa menu list * during the update process the old data will be returned for a API request */ private fun asyncUpdateMensa() = GlobalScope.launch { val mensaCurrentWeek = MensaParser().getMensaMenu(mensaMenuURL) val mensaNextWeek = MensaParser().getMensaMenu(MensaParser().getMenuLinkNextWeek(mensaMenuURL)) // only update if we get valid data if (mensaCurrentWeek != null && mensaNextWeek != null) { mensaMenu = MensaMenu(MensaMeta(System.currentTimeMillis() / 1000, mensaName), mensaCurrentWeek, mensaNextWeek) } logger.info("updated mensamenu successful at ${Date(mensaMenu.meta.updateTime * 1000)}") } /** * this function updates all existing timetables * during the update process the old data will be returned for a API request */ private fun asyncUpdateTimetables() = GlobalScope.launch { timetableList.forEach { timetableCourse -> val updateURL = timetableCourse.meta.link timetableCourse.timetable = TimetableParser().getTimeTable(updateURL) timetableCourse.meta.updateTime = System.currentTimeMillis() / 1000 } logger.info("updated ${timetableList.size} timetables successful!") } /** * before the APIController is up, get the data fist * runBlocking: otherwise the api would return no data to requests for a few seconds after startup */ private fun initUpdates() = runBlocking { // get all courses on startup val jobCourseUpdate = GlobalScope.async { val result = CourseListParser().getCourseLinks(courseListURL) if (result != null) { courseList = CourseList(CourseMeta(System.currentTimeMillis() / 1000, result.size), result) } } // get the current and next weeks mensa menus val jobMensa = GlobalScope.async{ val mensaCurrentWeek = MensaParser().getMensaMenu(mensaMenuURL) val mensaNextWeek = MensaParser().getMensaMenu(MensaParser().getMenuLinkNextWeek(mensaMenuURL)) // only update if we get valid data if (mensaCurrentWeek != null && mensaNextWeek != null) { mensaMenu = MensaMenu(MensaMeta(System.currentTimeMillis() / 1000, mensaName), mensaCurrentWeek, mensaNextWeek) } } jobCourseUpdate.await() jobMensa.await() logger.info("init updates successful") } /** * doesn't account the change between winter and summer time! * */ private fun scheduledUpdates() { val currentTime = System.currentTimeMillis() val initDelay24h = (86400000 - ((currentTime + 3600000) % 86400000)) + 60000 val initDelay3h = (10800000 - ((currentTime + 3600000) % 10800000)) + 60000 val initDelay1h = (3600000 - ((currentTime + 3600000) % 3600000)) + 60000 // update courseList every 24 hours (time in ms) Timer().scheduleAtFixedRate(initDelay24h, 86400000) { asyncUpdateCourseList() } // update all already existing timetables every 3 hours (time in ms) Timer().scheduleAtFixedRate(initDelay3h, 10800000) { asyncUpdateTimetables() } // update courses every hour (time in ms) Timer().scheduleAtFixedRate(initDelay1h, 3600000) { asyncUpdateMensa() } } }