This is the home of the new hsoApp!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

375 lines
14 KiB

/**
* ProjectLaogai
*
* Copyright 2019-2020 <seil0@mosad.xyz>
*
* 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.seil0.projectlaogai.controller.cache
import android.content.Context
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.util.*
import java.io.*
import java.util.*
import kotlin.collections.ArrayList
/**
* The cacheController reads and updates the cache files.
* It contains the courseList and mensaMenu object, all timetable objects
* are located in TimetableController.
*/
class CacheController(cont: Context) {
private val className = "CacheController"
private val context = cont
init {
val cal = Calendar.getInstance()
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
val currentTime = System.currentTimeMillis() / 1000
// check if we need to update the mensa data before displaying it
cal.time = Date(mensaCacheTime * 1000)
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
Log.i(className, "update mensa blocking")
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
}
// check if we need to update the timetable before displaying it
cal.time = Date(timetableCacheTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
Log.i(className, "updating timetable after sunday!")
GlobalScope.launch(Dispatchers.Default) {
val threads = listOf(
updateTimetable(cCourse.courseName, 0, context),
updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll()
}
}
updateCourseList(context)
readStartCache(cCourse.courseName) // initially read values from cache
// check if an update is necessary, not blocking
if (currentTime - coursesCacheTime > 86400)
updateCourseList(context)
if (currentTime - mensaCacheTime > 10800)
updateMensaMenu(context)
if (currentTime - timetableCacheTime > 10800) {
TimetableController.update(context)
}
}
companion object {
private const val className = "CacheController"
var coursesList = ArrayList<Course>()
var mensaMenu = MensaMenu()
/**
* update the course list, async
*/
fun updateCourseList(context: Context): Job {
val file = File(context.filesDir, "courses.json")
var courseListUp = CoursesList()
return GlobalScope.launch(Dispatchers.IO) {
try {
courseListUp = TCoRAPIController.getCourseListNEW()
} catch (ex: Exception) {
Log.e(className, "could not load course list from tcor", ex)
}
// update coursesList array list
coursesList = courseListUp.courses
// save cache file and update time
save(file, Gson().toJson(courseListUp))
coursesCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
/**
* update the mensa menu, async
*/
fun updateMensaMenu(context: Context): Job {
val file = File(context.filesDir, "mensa.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
mensaMenu = TCoRAPIController.getMensaMenu()
} catch (ex: Exception) {
Log.e(className, "could not load mensa menu from tcor", ex)
}
// save cache file and update time
save(file, Gson().toJson(mensaMenu))
mensaCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
/**
* update the timetable for a week, async
* @param courseName the course name (e.g AI1)
* @param week the week to update (0 for the current and so on)
*/
fun updateTimetable(courseName: String, week: Int, context: Context): Job {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
var timetable = TimetableWeek()
// try to update timetable from tcor, async
return GlobalScope.launch(Dispatchers.IO) {
try {
timetable = TCoRAPIController.getTimetable(courseName, week)
} catch (ex: Exception) {
Log.e(className, "could not load timetable $courseName[$week] from tcor", ex)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
// save cache file and update time
save(file, Gson().toJson(timetable))
timetableCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
/**
* update all additional subject lessons, async
*/
fun updateAdditionalLessons(context: Context): Job {
val fileLessons = File(context.filesDir, "additional_lessons.json")
return GlobalScope.launch(Dispatchers.IO) {
TimetableController.subjectMap.forEach { (courseName, subjects) ->
// update all subjects for a course
subjects.forEach {subject ->
try {
TCoRAPIController.getLessons(courseName, subject, 0).forEach { lesson ->
TimetableController.addLesson(courseName, subject, lesson)
}
} catch (ex: Exception) {
Log.e(className, "could not load $courseName: $subject", ex)
}
}
}
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
}
}
/**
* save changes in lessonMap and subjectMap,
* called on addSubject or removeSubject
*/
fun saveAdditionalSubjects(context: Context) {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
save(fileLessons, Gson().toJson(TimetableController.lessonMap))
save(fileSubjects, Gson().toJson(TimetableController.subjectMap))
}
private fun save(file: File, text: String) {
try {
val writer = BufferedWriter(FileWriter(file))
writer.write(text)
writer.close()
} catch (ex: Exception) {
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
}
}
}
/**
* read coursesList, mensa (current and next week), timetable (current and next week)
* @param courseName the course name (e.g AI1)
*/
private fun readStartCache(courseName: String) {
try {
readCoursesList()
} catch (ex : Exception) {
Log.e(className, "Error while reading the course list", ex)
}
try {
readMensa()
} catch (ex : Exception) {
Log.e(className, "Error while reading the mensa menu", ex)
}
try {
readTimetable(courseName, 0)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 0", ex)
}
try {
readTimetable(courseName, 1)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 1", ex)
}
try {
readAdditionalSubjects()
} catch (ex : Exception) {
Log.e(className, "Error while reading additional subjects", ex)
}
}
/**
* read the courses list from the cached file
* add them to the coursesList object
*/
private fun readCoursesList() {
val file = File(context.filesDir, "courses.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateCourseList(context).join() }
return
}
coursesList = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses
}
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
private fun readMensa() {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateMensaMenu(context).join() }
return
}
mensaMenu = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass)
}
}
/**
* read the weeks timetable from the cached file
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
private fun readTimetable(courseName: String, week: Int) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// if the file does not exist, call updateTimetable blocking and return
if (!file.exists()) {
runBlocking { updateTimetable(courseName, week, context).join() }
return
}
val timetableObject = FileReader(file).use {
JsonParser.parseString(BufferedReader(it).readLine()).asJsonObject
}
// if its a TimetableCourseWeek object migrate to TimetableWeek TODO remove converting at version 0.8
val timetable = if(timetableObject.has("meta")) {
Log.i(Companion.className, "trying to migrate TimetableCourseWeek to TimetableWeek")
val timetableWC = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
save(file, Gson().toJson(TimetableWeek(
timetableWC.meta.weekIndex,
timetableWC.meta.weekNumberYear,
timetableWC.timetable.days
)))
TimetableWeek(timetableWC.meta.weekIndex, timetableWC.meta.weekNumberYear, timetableWC.timetable.days)
} else {
Gson().fromJson(timetableObject, TimetableWeek().javaClass)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
}
private fun readAdditionalSubjects() {
val fileLessons = File(context.filesDir, "additional_lessons.json")
val fileSubjects = File(context.filesDir, "additional_subjects.json")
// make sure the file exists
if (!fileLessons.exists() || !fileSubjects.exists()) {
return
}
// clear the maps before loading, just to be save
TimetableController.lessonMap.clear()
TimetableController.subjectMap.clear()
// read subjects and lessons from cache file
FileReader(fileLessons).use {
TimetableController.lessonMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), object : TypeToken<HashMap<String, Lesson>>() {}.type)
)
}
FileReader(fileSubjects).use {
TimetableController.subjectMap.putAll(
GsonBuilder().create()
.fromJson(BufferedReader(it).readLine(), HashMap<String, ArrayList<String>>().javaClass)
)
}
// add lessons to timetable
TimetableController.lessonMap.forEach { (_, lesson) ->
TimetableController.addLessonToTimetable(lesson)
}
}
}

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close