From 132cf2df0e30706251ecde58d398ea5777b52715 Mon Sep 17 00:00:00 2001 From: Seil0 Date: Sun, 20 Oct 2019 11:52:44 +0200 Subject: [PATCH] reworked the CacheController Class --- .../mosad/thecitadelofricks/APIController.kt | 65 +---- .../thecitadelofricks/CacheController.kt | 190 ------------ .../controller/CacheController.kt | 276 ++++++++++++++++++ .../controller/CachetAPIController.kt | 36 +++ 4 files changed, 327 insertions(+), 240 deletions(-) delete mode 100644 src/main/kotlin/org/mosad/thecitadelofricks/CacheController.kt create mode 100644 src/main/kotlin/org/mosad/thecitadelofricks/controller/CacheController.kt create mode 100644 src/main/kotlin/org/mosad/thecitadelofricks/controller/CachetAPIController.kt diff --git a/src/main/kotlin/org/mosad/thecitadelofricks/APIController.kt b/src/main/kotlin/org/mosad/thecitadelofricks/APIController.kt index b3ff778..24afc5c 100644 --- a/src/main/kotlin/org/mosad/thecitadelofricks/APIController.kt +++ b/src/main/kotlin/org/mosad/thecitadelofricks/APIController.kt @@ -22,9 +22,13 @@ package org.mosad.thecitadelofricks -import org.mosad.thecitadelofricks.CacheController.Companion.courseList -import org.mosad.thecitadelofricks.CacheController.Companion.mensaMenu -import org.mosad.thecitadelofricks.CacheController.Companion.timetableList +import org.mosad.thecitadelofricks.controller.CacheController +import org.mosad.thecitadelofricks.controller.CacheController.Companion.courseList +import org.mosad.thecitadelofricks.controller.CacheController.Companion.getLesson +import org.mosad.thecitadelofricks.controller.CacheController.Companion.getLessonSubjectList +import org.mosad.thecitadelofricks.controller.CacheController.Companion.getTimetable +import org.mosad.thecitadelofricks.controller.CacheController.Companion.mensaMenu +import org.mosad.thecitadelofricks.controller.CacheController.Companion.timetableList import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.web.bind.annotation.RequestMapping @@ -35,22 +39,25 @@ import java.net.URL import java.time.LocalDateTime import java.util.* import kotlin.collections.ArrayList -import kotlin.collections.HashSet @RestController class APIController { private val logger: Logger = LoggerFactory.getLogger(APIController::class.java) - private val cache = CacheController() private val apiVersion = "1.1.3" private val softwareVersion = "1.1.5" private val startTime = System.currentTimeMillis() / 1000 - private var totalRequests = 0 + var totalRequests = 0 private var mensaMenuRequests = 0 private var timetableRequests = ArrayList() + init { + CacheController() // initialize the CacheController + } + + // TODO remove this with API version 1.2.0 @Deprecated("courses is replaced by courseList", replaceWith = ReplaceWith("courseList()")) @RequestMapping("/courses") fun courses(): CourseList { @@ -80,7 +87,7 @@ class APIController { logger.info("timetable request at ${LocalDateTime.now()}!") updateTimetableRequests(courseName) totalRequests++ - return cache.getTimetable(courseName, week) + return getTimetable(courseName, week) } @RequestMapping("/lessonSubjectList") @@ -111,49 +118,6 @@ class APIController { // non direct api functions - /** - * get every explicit lesson in a week - * @param courseName the name of the course to be requested - * @param weekIndex request week number (current week = 0) - * @return a HashSet of explicit lessons for one week - */ - private fun getLessonSubjectList(courseName: String, weekIndex: Int): HashSet { - val lessonSubjectList = ArrayList() - - // get every lesson subject for the given week - val flatMap = cache.getTimetable(courseName, weekIndex).timetable.days.flatMap { it.timeslots.asIterable() } - flatMap.forEach { - it.stream().filter { x -> x.lessonSubject.isNotEmpty() }.findAny().ifPresent { x -> lessonSubjectList.add(x.lessonSubject) } - } - - return HashSet(lessonSubjectList) - } - - /** - * get every explicit lesson in a week - * @param courseName the name of the course to be requested - * @param lessonSubject the lesson subject to be requested - * @param weekIndex request week number (current week = 0) - * @return a ArrayList of every lesson with lessonSubject for one week - */ - private fun getLesson(courseName: String, lessonSubject: String, weekIndex: Int): ArrayList { - val lessonList = ArrayList() - - // get all lessons from the weeks timetable - val flatMap = cache.getTimetable(courseName, weekIndex).timetable.days.flatMap { it.timeslots.asIterable() } - flatMap.forEach { - it.forEach { lesson -> - if(lesson.lessonSubject.contains(lessonSubject)) { - lessonList.add(lesson) - } - } - - //it.stream().filter { x -> x.lessonSubject.contains(lessonSubject) }.findAny().ifPresent { x -> println("${x.lessonSubject}, ${x.lessonTeacher}") } - } - - return lessonList - } - /** * if a timetable is requested update the request counter */ @@ -163,6 +127,7 @@ class APIController { } timetableRequests.stream().filter { x ->x.courseName == courseName }.findFirst().ifPresent { x -> x.requests++ } + // TODO Java 11 // timetableRequests.stream().filter { it.courseName == courseName }.findFirst().ifPresentOrElse({ // it.requests++ // }, { diff --git a/src/main/kotlin/org/mosad/thecitadelofricks/CacheController.kt b/src/main/kotlin/org/mosad/thecitadelofricks/CacheController.kt deleted file mode 100644 index 213a9bc..0000000 --- a/src/main/kotlin/org/mosad/thecitadelofricks/CacheController.kt +++ /dev/null @@ -1,190 +0,0 @@ -/** - * 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() - } - - /** - * get a timetable, since they may not cached, we need to make sure it's cached, otherwise download - * @param courseName the name of the course to be requested - * @param weekIndex request week number (current week = 0) - * @return the timetable of a course (courseName) - */ - 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 { - CourseListParser().getCourseLinks(courseListURL)?.let { - courseList = CourseList(CourseMeta(System.currentTimeMillis() / 1000, it.size), it) - } - - 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 { - CourseListParser().getCourseLinks(courseListURL)?.let { - courseList = CourseList(CourseMeta(System.currentTimeMillis() / 1000, it.size), it) - } - } - - // 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() - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/org/mosad/thecitadelofricks/controller/CacheController.kt b/src/main/kotlin/org/mosad/thecitadelofricks/controller/CacheController.kt new file mode 100644 index 0000000..9d6b65a --- /dev/null +++ b/src/main/kotlin/org/mosad/thecitadelofricks/controller/CacheController.kt @@ -0,0 +1,276 @@ +/** + * 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.controller + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.mosad.thecitadelofricks.* +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" + + init { + initUpdates() + scheduledUpdates() + //CachetAPIController.postTotalRequests(5) + } + + // cache objects + companion object{ + private val logger: Logger = LoggerFactory.getLogger(CacheController::class.java) + + lateinit var courseList: CourseList + lateinit var mensaMenu: MensaMenu + var timetableList = ArrayList() // this list contains all timetables + + /** + * get a timetable, since they may not cached, we need to make sure it's cached, otherwise download + * @param courseName the name of the course to be requested + * @param weekIndex request week number (current week = 0) + * @return the timetable of a course (courseName) + */ + 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) + } + + /** + * get every explicit lesson in a week + * @param courseName the name of the course to be requested + * @param weekIndex request week number (current week = 0) + * @return a HashSet of explicit lessons for one week + */ + fun getLessonSubjectList(courseName: String, weekIndex: Int): HashSet = runBlocking { + val lessonSubjectList = ArrayList() + + // get every lesson subject for the given week + val flatMap = getTimetable(courseName, weekIndex).timetable.days.flatMap { it.timeslots.asIterable() } + flatMap.forEach { + it.stream().filter { x -> x.lessonSubject.isNotEmpty() }.findAny().ifPresent { x -> lessonSubjectList.add(x.lessonSubject) } + } + + return@runBlocking HashSet(lessonSubjectList) + } + + /** + * get every explicit lesson in a week + * @param courseName the name of the course to be requested + * @param lessonSubject the lesson subject to be requested + * @param weekIndex request week number (current week = 0) + * @return a ArrayList of every lesson with lessonSubject for one week + */ + fun getLesson(courseName: String, lessonSubject: String, weekIndex: Int): ArrayList { + val lessonList = ArrayList() + + // get all lessons from the weeks timetable + val flatMap = getTimetable(courseName, weekIndex).timetable.days.flatMap { it.timeslots.asIterable() } + flatMap.forEach { + it.forEach { lesson -> + if(lesson.lessonSubject.contains(lessonSubject)) { + lessonList.add(lesson) + } + } + + // TODO Java 11 + //it.stream().filter { x -> x.lessonSubject.contains(lessonSubject) }.findAny().ifPresent { x -> println("${x.lessonSubject}, ${x.lessonTeacher}") } + } + + return lessonList + } + } + + /** + * this function updates the courseList + * during the update process the old data will be returned for a API request + */ + private fun asyncUpdateCourseList() = GlobalScope.launch { + CourseListParser().getCourseLinks(courseListURL)?.let { + courseList = + CourseList( + CourseMeta( + System.currentTimeMillis() / 1000, + it.size + ), it + ) + } + + 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 { + CourseListParser().getCourseLinks(courseListURL)?.let { + courseList = + CourseList( + CourseMeta( + System.currentTimeMillis() / 1000, + it.size + ), it + ) + } + } + + // 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() + } + + // post to status.mosad.xyz every hour + Timer().scheduleAtFixedRate(initDelay1h, 3600000) { + // TODO + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/mosad/thecitadelofricks/controller/CachetAPIController.kt b/src/main/kotlin/org/mosad/thecitadelofricks/controller/CachetAPIController.kt new file mode 100644 index 0000000..2161b18 --- /dev/null +++ b/src/main/kotlin/org/mosad/thecitadelofricks/controller/CachetAPIController.kt @@ -0,0 +1,36 @@ +package org.mosad.thecitadelofricks.controller + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.net.HttpURLConnection +import java.net.URL +import java.io.OutputStreamWriter + + +class CachetAPIController { + + companion object { + + private val logger: Logger = LoggerFactory.getLogger(CachetAPIController::class.java) + + fun postTotalRequests(totalRequests: Int) { + logger.info("sending post request ...") + + val currentTime = System.currentTimeMillis() / 1000 + val data = mapOf(Pair("value", totalRequests.toString()), Pair("timestamp", currentTime.toString())) + + val url = URL("https://status.mosad.xyz/api/v1/metrics/1/points") + val connection = url.openConnection() as HttpURLConnection + connection.setRequestProperty("X-Cachet-Token", "h0103Wix5xhhNH5tk5KU") + connection.requestMethod = "POST" + connection.doOutput = true + + val writer = OutputStreamWriter(connection.outputStream) + writer.write(data.toString()) + writer.flush() + writer.close() + } + + } + +} \ No newline at end of file