2019-03-17 18:12:36 +01:00
/ * *
* ProjectLaogai
*
2020-01-15 15:00:05 +01:00
* Copyright 2019 - 2020 < seil0 @mosad . xyz >
2019-03-17 18:12:36 +01:00
*
* 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 .
*
* /
2020-08-10 14:31:17 +02:00
package org.mosad.seil0.projectlaogai.controller.cache
2019-03-17 18:12:36 +01:00
import android.content.Context
2020-02-05 14:10:45 +01:00
import android.util.Log
2019-03-17 18:12:36 +01:00
import com.google.gson.Gson
2019-06-02 19:09:36 +02:00
import com.google.gson.GsonBuilder
2019-03-17 18:12:36 +01:00
import com.google.gson.JsonParser
2020-08-10 14:31:17 +02:00
import com.google.gson.reflect.TypeToken
2020-02-18 15:18:36 +01:00
import kotlinx.coroutines.*
2020-08-13 21:01:21 +02:00
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
2020-08-10 14:31:17 +02:00
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
2020-08-13 21:01:21 +02:00
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
2020-08-07 12:14:50 +02:00
import org.mosad.seil0.projectlaogai.util.*
import java.io.*
2019-06-26 15:00:43 +02:00
import java.util.*
2019-06-02 19:09:36 +02:00
import kotlin.collections.ArrayList
2020-08-28 21:38:44 +02:00
import kotlin.collections.HashMap
2019-03-17 18:12:36 +01:00
2020-08-07 12:14:50 +02:00
/ * *
2020-08-10 14:31:17 +02:00
* The cacheController reads and updates the cache files .
* It contains the courseList and mensaMenu object , all timetable objects
* are located in TimetableController .
2020-08-07 12:14:50 +02:00
* /
2020-09-17 23:23:05 +02:00
class CacheController ( private val context : Context ) {
2019-03-17 18:12:36 +01:00
2020-09-17 23:23:05 +02:00
private val className = this . javaClass . name
2019-03-17 18:12:36 +01:00
2019-06-24 22:41:45 +02:00
init {
2019-06-26 15:00:43 +02:00
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
2020-08-07 12:14:50 +02:00
cal . time = Date ( mensaCacheTime * 1000 )
2019-06-26 15:00:43 +02:00
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
2020-09-17 23:23:05 +02:00
if ( ( currentDay == Calendar . MONDAY && cal . get ( Calendar . DAY _OF _WEEK ) == Calendar . SUNDAY ) || ( currentTime - mensaCacheTime ) > 86400 ) {
Log . i ( className , " Update mensa blocking " )
2022-12-22 17:26:07 +01:00
CoroutineScope ( Dispatchers . Default ) . launch { updateMensaMenu ( context ) . join ( ) }
2019-06-26 15:00:43 +02:00
}
2020-08-10 14:31:17 +02:00
// check if we need to update the timetable before displaying it
2020-08-07 12:14:50 +02:00
cal . time = Date ( timetableCacheTime * 1000 )
2019-06-26 15:00:43 +02:00
2019-07-01 20:31:06 +02:00
// 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
2020-08-07 12:14:50 +02:00
if ( ( currentDay == Calendar . MONDAY && cal . get ( Calendar . DAY _OF _WEEK ) == Calendar . SUNDAY ) || ( currentTime - timetableCacheTime ) > 86400 ) {
2020-09-17 23:23:05 +02:00
Log . i ( className , " Updating timetable after sunday! " )
2019-06-26 15:00:43 +02:00
2022-12-22 17:26:07 +01:00
CoroutineScope ( Dispatchers . Default ) . launch {
2020-02-04 19:58:07 +01:00
val threads = listOf (
2020-09-02 15:36:03 +02:00
updateTimetable ( Preferences . course . courseName , 0 , context ) ,
updateTimetable ( Preferences . course . courseName , 1 , context )
2020-02-04 19:58:07 +01:00
)
threads . joinAll ( )
}
2019-06-26 15:00:43 +02:00
}
2020-08-07 12:14:50 +02:00
updateCourseList ( context )
2020-09-02 15:36:03 +02:00
readStartCache ( Preferences . course . courseName ) // initially read values from cache
2020-08-07 12:14:50 +02:00
2019-06-26 15:00:43 +02:00
// check if an update is necessary, not blocking
2020-08-07 12:14:50 +02:00
if ( currentTime - coursesCacheTime > 86400 )
updateCourseList ( context )
2019-06-26 15:00:43 +02:00
2020-08-07 12:14:50 +02:00
if ( currentTime - mensaCacheTime > 10800 )
updateMensaMenu ( context )
2019-06-26 15:00:43 +02:00
2020-09-17 23:23:05 +02:00
if ( currentTime - timetableCacheTime > 10800 )
2020-08-10 14:31:17 +02:00
TimetableController . update ( context )
2019-06-24 22:41:45 +02:00
}
2019-03-17 18:12:36 +01:00
companion object {
2020-08-07 12:14:50 +02:00
private const val className = " CacheController "
2019-03-17 18:12:36 +01:00
var coursesList = ArrayList < Course > ( )
2019-06-26 15:00:43 +02:00
var mensaMenu = MensaMenu ( )
/ * *
2020-08-07 12:14:50 +02:00
* update the course list , async
2019-06-26 15:00:43 +02:00
* /
2020-08-07 12:14:50 +02:00
fun updateCourseList ( context : Context ) : Job {
2019-06-26 15:00:43 +02:00
val file = File ( context . filesDir , " courses.json " )
2020-08-07 12:14:50 +02:00
var courseListUp = CoursesList ( )
2022-12-22 17:26:07 +01:00
return CoroutineScope ( Dispatchers . IO ) . launch {
2020-08-07 12:14:50 +02:00
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
2020-08-13 21:01:21 +02:00
Preferences . save ( context )
2020-08-07 12:14:50 +02:00
}
2019-06-26 15:00:43 +02:00
}
2019-04-03 19:21:51 +02:00
/ * *
2020-08-07 12:14:50 +02:00
* update the mensa menu , async
2019-04-03 19:21:51 +02:00
* /
2020-08-07 12:14:50 +02:00
fun updateMensaMenu ( context : Context ) : Job {
2019-04-03 19:21:51 +02:00
val file = File ( context . filesDir , " mensa.json " )
2022-12-22 17:26:07 +01:00
return CoroutineScope ( Dispatchers . IO ) . launch {
2020-08-07 12:14:50 +02:00
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
2020-08-13 21:01:21 +02:00
Preferences . save ( context )
2020-08-07 12:14:50 +02:00
}
2019-04-03 19:21:51 +02:00
}
2019-04-06 14:13:01 +02:00
/ * *
2020-08-07 12:14:50 +02:00
* update the timetable for a week , async
2019-04-06 14:13:01 +02:00
* @param courseName the course name ( e . g AI1 )
2020-08-07 12:14:50 +02:00
* @param week the week to update ( 0 for the current and so on )
2019-04-06 14:13:01 +02:00
* /
2020-08-07 12:14:50 +02:00
fun updateTimetable ( courseName : String , week : Int , context : Context ) : Job {
2019-04-06 14:13:01 +02:00
val file = File ( context . filesDir , " timetable- $courseName - $week .json " )
2020-08-07 12:14:50 +02:00
var timetable = TimetableWeek ( )
// try to update timetable from tcor, async
2022-12-22 17:26:07 +01:00
return CoroutineScope ( Dispatchers . IO ) . launch {
2020-08-07 12:14:50 +02:00
try {
timetable = TCoRAPIController . getTimetable ( courseName , week )
} catch ( ex : Exception ) {
2020-08-10 14:31:17 +02:00
Log . e ( className , " could not load timetable $courseName [ $week ] from tcor " , ex )
2020-08-07 12:14:50 +02:00
}
// 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
2020-08-13 21:01:21 +02:00
Preferences . save ( context )
2020-08-07 12:14:50 +02:00
}
}
2019-04-06 14:13:01 +02:00
2020-08-10 14:31:17 +02:00
/ * *
* update all additional subject lessons , async
* /
fun updateAdditionalLessons ( context : Context ) : Job {
val fileLessons = File ( context . filesDir , " additional_lessons.json " )
2022-12-22 17:26:07 +01:00
return CoroutineScope ( Dispatchers . IO ) . launch {
2020-08-10 14:31:17 +02:00
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 ) )
}
2020-08-07 12:14:50 +02:00
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 )
2019-04-06 14:13:01 +02:00
}
}
2019-03-17 18:12:36 +01:00
}
/ * *
* read coursesList , mensa ( current and next week ) , timetable ( current and next week )
* @param courseName the course name ( e . g AI1 )
* /
2020-02-05 14:10:45 +01:00
private fun readStartCache ( courseName : String ) {
2020-06-11 18:39:19 +02:00
try {
2020-08-10 14:31:17 +02:00
readCoursesList ( )
2020-08-07 12:14:50 +02:00
} catch ( ex : Exception ) {
Log . e ( className , " Error while reading the course list " , ex )
2020-06-11 18:39:19 +02:00
}
try {
2020-08-10 14:31:17 +02:00
readMensa ( )
2020-08-07 12:14:50 +02:00
} catch ( ex : Exception ) {
Log . e ( className , " Error while reading the mensa menu " , ex )
2020-06-11 18:39:19 +02:00
}
try {
2020-08-10 14:31:17 +02:00
readTimetable ( courseName , 0 )
2020-08-07 12:14:50 +02:00
} catch ( ex : Exception ) {
Log . e ( className , " Error while reading timetable week 0 " , ex )
2020-06-11 18:39:19 +02:00
}
try {
2020-08-10 14:31:17 +02:00
readTimetable ( courseName , 1 )
2020-08-07 12:14:50 +02:00
} catch ( ex : Exception ) {
Log . e ( className , " Error while reading timetable week 1 " , ex )
}
2020-08-10 14:31:17 +02:00
try {
readAdditionalSubjects ( )
} catch ( ex : Exception ) {
Log . e ( className , " Error while reading additional subjects " , ex )
}
2020-08-07 12:14:50 +02:00
}
/ * *
* read the courses list from the cached file
* add them to the coursesList object
* /
2020-08-10 14:31:17 +02:00
private fun readCoursesList ( ) {
2020-08-07 12:14:50 +02:00
val file = File ( context . filesDir , " courses.json " )
// make sure the file exists
if ( ! file . exists ( ) ) {
runBlocking { updateCourseList ( context ) . join ( ) }
return
2020-06-11 18:39:19 +02:00
}
2020-08-07 12:14:50 +02:00
coursesList = FileReader ( file ) . use {
2020-08-30 01:38:45 +02:00
GsonBuilder ( ) . create ( ) . fromJson ( it , CoursesList ( ) . javaClass ) . courses
2020-08-07 12:14:50 +02:00
}
}
/ * *
* get the MensaMenu object from the cached json ,
* if cache is empty create the cache file
* /
2020-08-10 14:31:17 +02:00
private fun readMensa ( ) {
2020-08-07 12:14:50 +02:00
val file = File ( context . filesDir , " mensa.json " )
// make sure the file exists
if ( ! file . exists ( ) ) {
runBlocking { updateMensaMenu ( context ) . join ( ) }
return
}
mensaMenu = FileReader ( file ) . use {
2020-08-30 01:38:45 +02:00
GsonBuilder ( ) . create ( ) . fromJson ( it , MensaMenu ( ) . javaClass )
2020-08-07 12:14:50 +02:00
}
}
/ * *
* 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 )
* /
2020-08-10 14:31:17 +02:00
private fun readTimetable ( courseName : String , week : Int ) {
2020-08-07 12:14:50 +02:00
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 )
}
2019-03-17 18:12:36 +01:00
}
2020-08-10 14:31:17 +02:00
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 ( )
2020-08-30 01:38:45 +02:00
. fromJson ( it , object : TypeToken < HashMap < String , Lesson > > ( ) { } . type )
2020-08-10 14:31:17 +02:00
)
}
FileReader ( fileSubjects ) . use {
TimetableController . subjectMap . putAll (
GsonBuilder ( ) . create ( )
2020-08-30 01:38:45 +02:00
. fromJson ( it , HashMap < String , ArrayList < String > > ( ) . javaClass )
2020-08-10 14:31:17 +02:00
)
}
// add lessons to timetable
TimetableController . lessonMap . forEach { ( _ , lesson ) ->
TimetableController . addLessonToTimetable ( lesson )
}
}
2019-03-17 18:12:36 +01:00
}